From 5e82e6055134588678416ba6ab4e8d4e139d4793 Mon Sep 17 00:00:00 2001 From: Alex McKinney Date: Tue, 1 Oct 2024 13:16:09 -0400 Subject: [PATCH 01/10] feat(seed): Always include docker logs (#4784) --- .../docker-utils/package.json | 1 + .../src/__test__/runDocker.test.ts | 2 ++ .../docker-utils/src/runDocker.ts | 22 ++++++++++++------- .../src/runGenerator.ts | 2 ++ pnpm-lock.yaml | 3 +++ 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/cli/generation/local-generation/docker-utils/package.json b/packages/cli/generation/local-generation/docker-utils/package.json index b8ba613667a..8cd209c1e07 100644 --- a/packages/cli/generation/local-generation/docker-utils/package.json +++ b/packages/cli/generation/local-generation/docker-utils/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@fern-api/fs-utils": "workspace:*", + "@fern-api/logger": "workspace:*", "dockerode": "^4.0.2", "tmp-promise": "^3.0.3" }, diff --git a/packages/cli/generation/local-generation/docker-utils/src/__test__/runDocker.test.ts b/packages/cli/generation/local-generation/docker-utils/src/__test__/runDocker.test.ts index 572819b1b87..78efcf633ab 100644 --- a/packages/cli/generation/local-generation/docker-utils/src/__test__/runDocker.test.ts +++ b/packages/cli/generation/local-generation/docker-utils/src/__test__/runDocker.test.ts @@ -1,4 +1,5 @@ import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; +import { CONSOLE_LOGGER } from "@fern-api/logger"; import { exec } from "child_process"; import { mkdir, rm } from "fs/promises"; import path from "path"; @@ -33,6 +34,7 @@ describe("runDocker", () => { const expectedOutputFilePath = "my-file.txt"; await runDocker({ + logger: CONSOLE_LOGGER, imageName: BASIC_WRITER_IMAGE_NAME, args: [expectedOutputFilePath], binds: [`${HOST_OUTPUT_DIR}:${IMAGE_OUTPUT_DIR}`] diff --git a/packages/cli/generation/local-generation/docker-utils/src/runDocker.ts b/packages/cli/generation/local-generation/docker-utils/src/runDocker.ts index 24e5252df71..bc0a264d581 100644 --- a/packages/cli/generation/local-generation/docker-utils/src/runDocker.ts +++ b/packages/cli/generation/local-generation/docker-utils/src/runDocker.ts @@ -1,3 +1,4 @@ +import { Logger } from "@fern-api/logger"; import Docker from "dockerode"; import { writeFile } from "fs/promises"; import { Writable } from "stream"; @@ -5,6 +6,7 @@ import tmp from "tmp-promise"; export declare namespace runDocker { export interface Args { + logger: Logger; imageName: string; args?: string[]; binds?: string[]; @@ -18,6 +20,7 @@ export declare namespace runDocker { } export async function runDocker({ + logger, imageName, args, binds, @@ -25,7 +28,8 @@ export async function runDocker({ removeAfterCompletion = false }: runDocker.Args): Promise { const docker = new Docker(); - const tryRun = () => tryRunDocker({ docker, imageName, args, binds, removeAfterCompletion, writeLogsToFile }); + const tryRun = () => + tryRunDocker({ logger, docker, imageName, args, binds, removeAfterCompletion, writeLogsToFile }); try { await tryRun(); } catch (e) { @@ -41,6 +45,7 @@ export async function runDocker({ // may throw a 404 if the image hasn't been downloaded async function tryRunDocker({ + logger, docker, imageName, args, @@ -48,6 +53,7 @@ async function tryRunDocker({ removeAfterCompletion, writeLogsToFile }: { + logger: Logger; docker: Docker; imageName: string; args?: string[]; @@ -81,14 +87,14 @@ async function tryRunDocker({ throw status.Error; } + if (writeLogsToFile) { + const tmpFile = await tmp.file(); + await writeFile(tmpFile.path, logs); + logger.info(`Generator logs here: ${tmpFile.path}`); + } + if (status.StatusCode !== 0) { - if (writeLogsToFile) { - const tmpFile = await tmp.file(); - await writeFile(tmpFile.path, logs); - throw new Error(`Docker exited with a non-zero exit code. Logs here: ${tmpFile.path}`); - } else { - throw new Error("Docker exited with a non-zero exit code."); - } + throw new Error("Docker exited with a non-zero exit code."); } } diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts index 21bcee879a2..1b3de03dd86 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts @@ -193,6 +193,7 @@ export async function runGenerator({ absolutePathToIr, absolutePathToWriteConfigJson, keepDocker, + context, generatorInvocation, writeUnitTests, generateOauthClients, @@ -238,6 +239,7 @@ export async function runGenerator({ } await runDocker({ + logger: context.logger, imageName, args: [DOCKER_GENERATOR_CONFIG_PATH], binds, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b02cd8e0f1..bdd7acdfd50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4286,6 +4286,9 @@ importers: '@fern-api/fs-utils': specifier: workspace:* version: link:../../../../commons/fs-utils + '@fern-api/logger': + specifier: workspace:* + version: link:../../../logger dockerode: specifier: ^4.0.2 version: 4.0.2 From 0fddeb207fe16c7f3bc4ba2fdbc08b7de6bf982e Mon Sep 17 00:00:00 2001 From: Deep Singhvi Date: Tue, 1 Oct 2024 15:25:49 -0400 Subject: [PATCH 02/10] fix(typescript): file arrays call appendFile (#4787) --- generators/typescript/sdk/CHANGELOG.md | 5 +++++ generators/typescript/sdk/VERSION | 2 +- .../src/endpoints/utils/appendPropertyToFormData.ts | 2 +- pnpm-lock.yaml | 2 ++ .../src/api/resources/service/client/Client.ts | 4 ++-- .../src/api/resources/service/client/Client.ts | 4 ++-- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/generators/typescript/sdk/CHANGELOG.md b/generators/typescript/sdk/CHANGELOG.md index 0ffec774d58..497c462e5b5 100644 --- a/generators/typescript/sdk/CHANGELOG.md +++ b/generators/typescript/sdk/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.40.8] - 2024-09-28 + +- Fix: File array uploads now call `request.appendFile` instead of `request.append` which + was causing form data to be in a corrupted state. + ## [0.40.7] - 2024-09-28 - Fix: The generated README will now have a section that links to the generated diff --git a/generators/typescript/sdk/VERSION b/generators/typescript/sdk/VERSION index 7754b3ee7fd..e374d018191 100644 --- a/generators/typescript/sdk/VERSION +++ b/generators/typescript/sdk/VERSION @@ -1 +1 @@ -0.40.7 +0.40.8 diff --git a/generators/typescript/sdk/client-class-generator/src/endpoints/utils/appendPropertyToFormData.ts b/generators/typescript/sdk/client-class-generator/src/endpoints/utils/appendPropertyToFormData.ts index 2a62757e1a9..38cfe41fcdf 100644 --- a/generators/typescript/sdk/client-class-generator/src/endpoints/utils/appendPropertyToFormData.ts +++ b/generators/typescript/sdk/client-class-generator/src/endpoints/utils/appendPropertyToFormData.ts @@ -60,7 +60,7 @@ export function appendPropertyToFormData({ ), ts.factory.createBlock( [ - context.coreUtilities.formDataUtils.append({ + context.coreUtilities.formDataUtils.appendFile({ referencetoFormData: referenceToFormData, key: property.key.wireValue, value: ts.factory.createIdentifier(FOR_LOOP_ITEM_VARIABLE_NAME) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdd7acdfd50..fdae32e9512 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3333,6 +3333,8 @@ importers: packages/cli/cli/dist/dev: {} + packages/cli/cli/dist/prod: {} + packages/cli/configuration: dependencies: '@fern-api/core-utils': diff --git a/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts b/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts index c93359af6d0..7ccfbf75359 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts @@ -54,7 +54,7 @@ export class Service { await _request.append("integer", request.integer.toString()); await _request.appendFile("file", file); for (const _file of fileList) { - await _request.append("fileList", _file); + await _request.appendFile("fileList", _file); } if (maybeFile != null) { @@ -63,7 +63,7 @@ export class Service { if (maybeFileList != null) { for (const _file of maybeFileList) { - await _request.append("maybeFileList", _file); + await _request.appendFile("maybeFileList", _file); } } diff --git a/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts b/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts index fdcf7355512..ba5d0251d5b 100644 --- a/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts @@ -45,7 +45,7 @@ export class Service { await _request.append("integer", request.integer.toString()); await _request.appendFile("file", request.file); for (const _file of request.fileList) { - await _request.append("fileList", _file); + await _request.appendFile("fileList", _file); } if (request.maybeFile != null) { @@ -54,7 +54,7 @@ export class Service { if (request.maybeFileList != null) { for (const _file of request.maybeFileList) { - await _request.append("maybeFileList", _file); + await _request.appendFile("maybeFileList", _file); } } From 4751f47a2b856bfff7686ecc20f711d8f98e0845 Mon Sep 17 00:00:00 2001 From: Dan Burke Date: Tue, 1 Oct 2024 17:04:13 -0400 Subject: [PATCH 03/10] feat(php): undiscriminated unions (#4783) --- generators/php/codegen/src/AsIs.ts | 1 + .../codegen/src/asIs/JsonDecoder.Template.php | 42 +++- .../src/asIs/JsonDeserializer.Template.php | 37 +++- .../src/asIs/JsonSerializer.Template.php | 33 ++- .../src/asIs/SerializableType.Template.php | 16 +- .../php/codegen/src/asIs/Union.Template.php | 55 ++++- .../asIs/UnionPropertyTypeTest.Template.php | 118 ++++++++++ generators/php/codegen/src/ast/Attribute.ts | 7 +- generators/php/codegen/src/ast/Type.ts | 37 +++- .../context/AbstractPhpGeneratorContext.ts | 3 +- .../codegen/src/context/PhpAttributeMapper.ts | 57 +++-- .../php/codegen/src/context/PhpTypeMapper.ts | 11 +- .../endpoint/http/HttpEndpointGenerator.ts | 34 ++- .../src/endpoint/request/EndpointRequest.ts | 60 +++++- .../request/WrappedEndpointRequest.ts | 11 +- generators/php/sdk/versions.yml | 5 + .../alias-extends/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../alias-extends/src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../alias-extends/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-model/alias/src/Core/JsonDecoder.php | 13 ++ .../alias/src/Core/JsonDeserializer.php | 37 +++- .../alias/src/Core/JsonSerializer.php | 33 ++- .../alias/src/Core/SerializableType.php | 14 ++ seed/php-model/alias/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../any-auth/src/Core/JsonDecoder.php | 13 ++ .../any-auth/src/Core/JsonDeserializer.php | 37 +++- .../any-auth/src/Core/JsonSerializer.php | 33 ++- .../any-auth/src/Core/SerializableType.php | 14 ++ seed/php-model/any-auth/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../api-wide-base-path/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../audiences/src/Core/JsonDecoder.php | 13 ++ .../audiences/src/Core/JsonDeserializer.php | 37 +++- .../audiences/src/Core/JsonSerializer.php | 33 ++- .../audiences/src/Core/SerializableType.php | 14 ++ seed/php-model/audiences/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../basic-auth/src/Core/JsonDecoder.php | 13 ++ .../basic-auth/src/Core/JsonDeserializer.php | 37 +++- .../basic-auth/src/Core/JsonSerializer.php | 33 ++- .../basic-auth/src/Core/SerializableType.php | 14 ++ seed/php-model/basic-auth/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-model/bytes/src/Core/JsonDecoder.php | 13 ++ .../bytes/src/Core/JsonDeserializer.php | 37 +++- .../bytes/src/Core/JsonSerializer.php | 33 ++- .../bytes/src/Core/SerializableType.php | 14 ++ seed/php-model/bytes/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../circular-references/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../custom-auth/src/Core/JsonDecoder.php | 13 ++ .../custom-auth/src/Core/JsonDeserializer.php | 37 +++- .../custom-auth/src/Core/JsonSerializer.php | 33 ++- .../custom-auth/src/Core/SerializableType.php | 14 ++ seed/php-model/custom-auth/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-model/enum/src/Core/JsonDecoder.php | 13 ++ .../enum/src/Core/JsonDeserializer.php | 37 +++- .../enum/src/Core/JsonSerializer.php | 33 ++- .../enum/src/Core/SerializableType.php | 14 ++ seed/php-model/enum/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../error-property/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../error-property/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../examples/src/Core/JsonDecoder.php | 13 ++ .../examples/src/Core/JsonDeserializer.php | 37 +++- .../examples/src/Core/JsonSerializer.php | 33 ++- .../examples/src/Core/SerializableType.php | 14 ++ seed/php-model/examples/src/Core/Union.php | 49 ++++- seed/php-model/examples/src/Identifier.php | 6 +- seed/php-model/examples/src/Types/Entity.php | 8 +- .../examples/src/Types/ResponseType.php | 8 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../exhaustive/src/Core/JsonDecoder.php | 13 ++ .../exhaustive/src/Core/JsonDeserializer.php | 37 +++- .../exhaustive/src/Core/JsonSerializer.php | 33 ++- .../exhaustive/src/Core/SerializableType.php | 14 ++ seed/php-model/exhaustive/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../extends/src/Core/JsonDecoder.php | 13 ++ .../extends/src/Core/JsonDeserializer.php | 37 +++- .../extends/src/Core/JsonSerializer.php | 33 ++- .../extends/src/Core/SerializableType.php | 14 ++ seed/php-model/extends/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../extra-properties/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../extra-properties/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../file-download/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../file-download/src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../file-download/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../file-upload/src/Core/JsonDecoder.php | 13 ++ .../file-upload/src/Core/JsonDeserializer.php | 37 +++- .../file-upload/src/Core/JsonSerializer.php | 33 ++- .../file-upload/src/Core/SerializableType.php | 14 ++ seed/php-model/file-upload/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../folders/src/Core/JsonDecoder.php | 13 ++ .../folders/src/Core/JsonDeserializer.php | 37 +++- .../folders/src/Core/JsonSerializer.php | 33 ++- .../folders/src/Core/SerializableType.php | 14 ++ seed/php-model/folders/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../grpc-proto-exhaustive/src/Column.php | 11 +- .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../grpc-proto-exhaustive/src/Core/Union.php | 49 ++++- .../grpc-proto-exhaustive/src/QueryColumn.php | 11 +- .../src/ScoredColumn.php | 11 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../grpc-proto/src/Core/JsonDecoder.php | 13 ++ .../grpc-proto/src/Core/JsonDeserializer.php | 37 +++- .../grpc-proto/src/Core/JsonSerializer.php | 33 ++- .../grpc-proto/src/Core/SerializableType.php | 14 ++ seed/php-model/grpc-proto/src/Core/Union.php | 49 ++++- seed/php-model/grpc-proto/src/UserModel.php | 13 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../idempotency-headers/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-model/imdb/src/Core/JsonDecoder.php | 13 ++ .../imdb/src/Core/JsonDeserializer.php | 37 +++- .../imdb/src/Core/JsonSerializer.php | 33 ++- .../imdb/src/Core/SerializableType.php | 14 ++ seed/php-model/imdb/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../literal/src/Core/JsonDecoder.php | 13 ++ .../literal/src/Core/JsonDeserializer.php | 37 +++- .../literal/src/Core/JsonSerializer.php | 33 ++- .../literal/src/Core/SerializableType.php | 14 ++ seed/php-model/literal/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../mixed-case/src/Core/JsonDecoder.php | 13 ++ .../mixed-case/src/Core/JsonDeserializer.php | 37 +++- .../mixed-case/src/Core/JsonSerializer.php | 33 ++- .../mixed-case/src/Core/SerializableType.php | 14 ++ seed/php-model/mixed-case/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../mixed-file-directory/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../multi-line-docs/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../multi-line-docs/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../multi-url-environment/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../no-environment/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../no-environment/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../php-model/object/src/Core/JsonDecoder.php | 13 ++ .../object/src/Core/JsonDeserializer.php | 37 +++- .../object/src/Core/JsonSerializer.php | 33 ++- .../object/src/Core/SerializableType.php | 14 ++ seed/php-model/object/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../objects-with-imports/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../optional/src/Core/JsonDecoder.php | 13 ++ .../optional/src/Core/JsonDeserializer.php | 37 +++- .../optional/src/Core/JsonSerializer.php | 33 ++- .../optional/src/Core/SerializableType.php | 14 ++ seed/php-model/optional/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../package-yml/src/Core/JsonDecoder.php | 13 ++ .../package-yml/src/Core/JsonDeserializer.php | 37 +++- .../package-yml/src/Core/JsonSerializer.php | 33 ++- .../package-yml/src/Core/SerializableType.php | 14 ++ seed/php-model/package-yml/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../pagination/src/Core/JsonDecoder.php | 13 ++ .../pagination/src/Core/JsonDeserializer.php | 37 +++- .../pagination/src/Core/JsonSerializer.php | 33 ++- .../pagination/src/Core/SerializableType.php | 14 ++ seed/php-model/pagination/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../plain-text/src/Core/JsonDecoder.php | 13 ++ .../plain-text/src/Core/JsonDeserializer.php | 37 +++- .../plain-text/src/Core/JsonSerializer.php | 33 ++- .../plain-text/src/Core/SerializableType.php | 14 ++ seed/php-model/plain-text/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../query-parameters/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../query-parameters/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../reserved-keywords/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../response-property/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../.github/workflows/ci.yml | 48 +++++ .../server-sent-event-examples/.gitignore | 4 + .../.mock/definition/api.yml | 1 + .../.mock/definition/completions.yml | 36 ++++ .../.mock/fern.config.json | 1 + .../.mock/generators.yml | 1 + .../server-sent-event-examples/composer.json | 40 ++++ .../server-sent-event-examples/phpstan.neon | 5 + .../server-sent-event-examples/phpunit.xml | 7 + .../snippet-templates.json | 0 .../server-sent-event-examples/snippet.json | 0 .../src/Completions/StreamedCompletion.php | 34 +++ .../src/Core/ArrayType.php | 16 ++ .../src/Core/Constant.php | 12 ++ .../src/Core/DateType.php | 16 ++ .../src/Core/JsonDecoder.php | 160 ++++++++++++++ .../src/Core/JsonDeserializer.php | 202 ++++++++++++++++++ .../src/Core/JsonEncoder.php | 20 ++ .../src/Core/JsonProperty.php | 13 ++ .../src/Core/JsonSerializer.php | 190 ++++++++++++++++ .../src/Core/SerializableType.php | 179 ++++++++++++++++ .../src/Core/Union.php | 62 ++++++ .../src/Core/Utils.php | 61 ++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/EnumTest.php | 76 +++++++ .../tests/Seed/Core/InvalidTypesTest.php | 45 ++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ++++++ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 +++++++++ .../tests/Seed/Core/NullPropertyTypeTest.php | 50 +++++ .../tests/Seed/Core/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/TestTypeTest.php | 201 +++++++++++++++++ .../tests/Seed/Core/UnionArrayTypeTest.php | 56 +++++ .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../.github/workflows/ci.yml | 48 +++++ seed/php-model/server-sent-events/.gitignore | 4 + .../.mock/definition/api.yml | 1 + .../.mock/definition/completions.yml | 22 ++ .../server-sent-events/.mock/fern.config.json | 1 + .../server-sent-events/.mock/generators.yml | 1 + .../server-sent-events/composer.json | 40 ++++ .../php-model/server-sent-events/phpstan.neon | 5 + seed/php-model/server-sent-events/phpunit.xml | 7 + .../server-sent-events/snippet-templates.json | 0 .../php-model/server-sent-events/snippet.json | 0 .../src/Completions/StreamedCompletion.php | 34 +++ .../server-sent-events/src/Core/ArrayType.php | 16 ++ .../server-sent-events/src/Core/Constant.php | 12 ++ .../server-sent-events/src/Core/DateType.php | 16 ++ .../src/Core/JsonDecoder.php | 160 ++++++++++++++ .../src/Core/JsonDeserializer.php | 202 ++++++++++++++++++ .../src/Core/JsonEncoder.php | 20 ++ .../src/Core/JsonProperty.php | 13 ++ .../src/Core/JsonSerializer.php | 190 ++++++++++++++++ .../src/Core/SerializableType.php | 179 ++++++++++++++++ .../server-sent-events/src/Core/Union.php | 62 ++++++ .../server-sent-events/src/Core/Utils.php | 61 ++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/EnumTest.php | 76 +++++++ .../tests/Seed/Core/InvalidTypesTest.php | 45 ++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ++++++ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 +++++++++ .../tests/Seed/Core/NullPropertyTypeTest.php | 50 +++++ .../tests/Seed/Core/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/TestTypeTest.php | 201 +++++++++++++++++ .../tests/Seed/Core/UnionArrayTypeTest.php | 56 +++++ .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../simple-fhir/src/BaseResource.php | 7 +- .../simple-fhir/src/Core/JsonDecoder.php | 13 ++ .../simple-fhir/src/Core/JsonDeserializer.php | 37 +++- .../simple-fhir/src/Core/JsonSerializer.php | 33 ++- .../simple-fhir/src/Core/SerializableType.php | 14 ++ seed/php-model/simple-fhir/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../streaming-parameter/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../streaming/src/Core/JsonDecoder.php | 13 ++ .../streaming/src/Core/JsonDeserializer.php | 37 +++- .../streaming/src/Core/JsonSerializer.php | 33 ++- .../streaming/src/Core/SerializableType.php | 14 ++ seed/php-model/streaming/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-model/trace/src/Core/JsonDecoder.php | 13 ++ .../trace/src/Core/JsonDeserializer.php | 37 +++- .../trace/src/Core/JsonSerializer.php | 33 ++- .../trace/src/Core/SerializableType.php | 14 ++ seed/php-model/trace/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../undiscriminated-unions/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../php-model/unions/src/Core/JsonDecoder.php | 13 ++ .../unions/src/Core/JsonDeserializer.php | 37 +++- .../unions/src/Core/JsonSerializer.php | 33 ++- .../unions/src/Core/SerializableType.php | 14 ++ seed/php-model/unions/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../unknown/src/Core/JsonDecoder.php | 13 ++ .../unknown/src/Core/JsonDeserializer.php | 37 +++- .../unknown/src/Core/JsonSerializer.php | 33 ++- .../unknown/src/Core/SerializableType.php | 14 ++ seed/php-model/unknown/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../validation/src/Core/JsonDecoder.php | 13 ++ .../validation/src/Core/JsonDeserializer.php | 37 +++- .../validation/src/Core/JsonSerializer.php | 33 ++- .../validation/src/Core/SerializableType.php | 14 ++ seed/php-model/validation/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../variables/src/Core/JsonDecoder.php | 13 ++ .../variables/src/Core/JsonDeserializer.php | 37 +++- .../variables/src/Core/JsonSerializer.php | 33 ++- .../variables/src/Core/SerializableType.php | 14 ++ seed/php-model/variables/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../version-no-default/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../version/src/Core/JsonDecoder.php | 13 ++ .../version/src/Core/JsonDeserializer.php | 37 +++- .../version/src/Core/JsonSerializer.php | 33 ++- .../version/src/Core/SerializableType.php | 14 ++ seed/php-model/version/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../websocket/src/Core/JsonDecoder.php | 13 ++ .../websocket/src/Core/JsonDeserializer.php | 37 +++- .../websocket/src/Core/JsonSerializer.php | 33 ++- .../websocket/src/Core/SerializableType.php | 14 ++ seed/php-model/websocket/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../alias-extends/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../alias-extends/src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ seed/php-sdk/alias-extends/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/alias/src/Core/JsonDecoder.php | 13 ++ .../alias/src/Core/JsonDeserializer.php | 37 +++- .../php-sdk/alias/src/Core/JsonSerializer.php | 33 ++- .../alias/src/Core/SerializableType.php | 14 ++ seed/php-sdk/alias/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../php-sdk/any-auth/src/Core/JsonDecoder.php | 13 ++ .../any-auth/src/Core/JsonDeserializer.php | 37 +++- .../any-auth/src/Core/JsonSerializer.php | 33 ++- .../any-auth/src/Core/SerializableType.php | 14 ++ seed/php-sdk/any-auth/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../api-wide-base-path/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../audiences/src/Core/JsonDecoder.php | 13 ++ .../audiences/src/Core/JsonDeserializer.php | 37 +++- .../audiences/src/Core/JsonSerializer.php | 33 ++- .../audiences/src/Core/SerializableType.php | 14 ++ seed/php-sdk/audiences/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../basic-auth/src/Core/JsonDecoder.php | 13 ++ .../basic-auth/src/Core/JsonDeserializer.php | 37 +++- .../basic-auth/src/Core/JsonSerializer.php | 33 ++- .../basic-auth/src/Core/SerializableType.php | 14 ++ seed/php-sdk/basic-auth/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/bytes/src/Core/JsonDecoder.php | 13 ++ .../bytes/src/Core/JsonDeserializer.php | 37 +++- .../php-sdk/bytes/src/Core/JsonSerializer.php | 33 ++- .../bytes/src/Core/SerializableType.php | 14 ++ seed/php-sdk/bytes/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../circular-references/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../custom-auth/src/Core/JsonDecoder.php | 13 ++ .../custom-auth/src/Core/JsonDeserializer.php | 37 +++- .../custom-auth/src/Core/JsonSerializer.php | 33 ++- .../custom-auth/src/Core/SerializableType.php | 14 ++ seed/php-sdk/custom-auth/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/enum/src/Core/JsonDecoder.php | 13 ++ .../enum/src/Core/JsonDeserializer.php | 37 +++- seed/php-sdk/enum/src/Core/JsonSerializer.php | 33 ++- .../enum/src/Core/SerializableType.php | 14 ++ seed/php-sdk/enum/src/Core/Union.php | 49 ++++- .../Requests/SendEnumInlinedRequest.php | 15 +- .../enum/src/PathParam/PathParamClient.php | 7 +- .../Requests/SendEnumAsQueryParamRequest.php | 15 +- .../SendEnumListAsQueryParamRequest.php | 9 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../error-property/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../php-sdk/error-property/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../php-sdk/examples/src/Core/JsonDecoder.php | 13 ++ .../examples/src/Core/JsonDeserializer.php | 37 +++- .../examples/src/Core/JsonSerializer.php | 33 ++- .../examples/src/Core/SerializableType.php | 14 ++ seed/php-sdk/examples/src/Core/Union.php | 49 ++++- .../php-sdk/examples/src/Types/Identifier.php | 6 +- .../examples/src/Types/Types/Entity.php | 8 +- .../examples/src/Types/Types/ResponseType.php | 8 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../exhaustive/src/Core/JsonDecoder.php | 13 ++ .../exhaustive/src/Core/JsonDeserializer.php | 37 +++- .../exhaustive/src/Core/JsonSerializer.php | 33 ++- .../exhaustive/src/Core/SerializableType.php | 14 ++ seed/php-sdk/exhaustive/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/extends/src/Core/JsonDecoder.php | 13 ++ .../extends/src/Core/JsonDeserializer.php | 37 +++- .../extends/src/Core/JsonSerializer.php | 33 ++- .../extends/src/Core/SerializableType.php | 14 ++ seed/php-sdk/extends/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../extra-properties/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../extra-properties/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../file-download/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../file-download/src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ seed/php-sdk/file-download/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../file-upload/src/Core/JsonDecoder.php | 13 ++ .../file-upload/src/Core/JsonDeserializer.php | 37 +++- .../file-upload/src/Core/JsonSerializer.php | 33 ++- .../file-upload/src/Core/SerializableType.php | 14 ++ seed/php-sdk/file-upload/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/folders/src/Core/JsonDecoder.php | 13 ++ .../folders/src/Core/JsonDeserializer.php | 37 +++- .../folders/src/Core/JsonSerializer.php | 33 ++- .../folders/src/Core/SerializableType.php | 14 ++ seed/php-sdk/folders/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../grpc-proto-exhaustive/src/Core/Union.php | 49 ++++- .../Dataservice/Requests/DeleteRequest.php | 13 +- .../Dataservice/Requests/DescribeRequest.php | 13 +- .../src/Dataservice/Requests/QueryRequest.php | 11 +- .../Dataservice/Requests/UpdateRequest.php | 11 +- .../src/Types/Column.php | 11 +- .../src/Types/QueryColumn.php | 11 +- .../src/Types/ScoredColumn.php | 11 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../grpc-proto/src/Core/JsonDecoder.php | 13 ++ .../grpc-proto/src/Core/JsonDeserializer.php | 37 +++- .../grpc-proto/src/Core/JsonSerializer.php | 33 ++- .../grpc-proto/src/Core/SerializableType.php | 14 ++ seed/php-sdk/grpc-proto/src/Core/Union.php | 49 ++++- .../grpc-proto/src/Types/UserModel.php | 13 +- .../Userservice/Requests/CreateRequest.php | 13 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../idempotency-headers/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/imdb/src/Core/JsonDecoder.php | 13 ++ .../imdb/src/Core/JsonDeserializer.php | 37 +++- seed/php-sdk/imdb/src/Core/JsonSerializer.php | 33 ++- .../imdb/src/Core/SerializableType.php | 14 ++ seed/php-sdk/imdb/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/literal/src/Core/JsonDecoder.php | 13 ++ .../literal/src/Core/JsonDeserializer.php | 37 +++- .../literal/src/Core/JsonSerializer.php | 33 ++- .../literal/src/Core/SerializableType.php | 14 ++ seed/php-sdk/literal/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../mixed-case/src/Core/JsonDecoder.php | 13 ++ .../mixed-case/src/Core/JsonDeserializer.php | 37 +++- .../mixed-case/src/Core/JsonSerializer.php | 33 ++- .../mixed-case/src/Core/SerializableType.php | 14 ++ seed/php-sdk/mixed-case/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../mixed-file-directory/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../multi-line-docs/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../multi-line-docs/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../no-environment/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../php-sdk/no-environment/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/object/src/Core/JsonDecoder.php | 13 ++ .../object/src/Core/JsonDeserializer.php | 37 +++- .../object/src/Core/JsonSerializer.php | 33 ++- .../object/src/Core/SerializableType.php | 14 ++ seed/php-sdk/object/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../objects-with-imports/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../php-sdk/optional/src/Core/JsonDecoder.php | 13 ++ .../optional/src/Core/JsonDeserializer.php | 37 +++- .../optional/src/Core/JsonSerializer.php | 33 ++- .../optional/src/Core/SerializableType.php | 14 ++ seed/php-sdk/optional/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../package-yml/src/Core/JsonDecoder.php | 13 ++ .../package-yml/src/Core/JsonDeserializer.php | 37 +++- .../package-yml/src/Core/JsonSerializer.php | 33 ++- .../package-yml/src/Core/SerializableType.php | 14 ++ seed/php-sdk/package-yml/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../pagination/src/Core/JsonDecoder.php | 13 ++ .../pagination/src/Core/JsonDeserializer.php | 37 +++- .../pagination/src/Core/JsonSerializer.php | 33 ++- .../pagination/src/Core/SerializableType.php | 14 ++ seed/php-sdk/pagination/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../plain-text/src/Core/JsonDecoder.php | 13 ++ .../plain-text/src/Core/JsonDeserializer.php | 37 +++- .../plain-text/src/Core/JsonSerializer.php | 33 ++- .../plain-text/src/Core/SerializableType.php | 14 ++ seed/php-sdk/plain-text/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../query-parameters/src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../query-parameters/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../reserved-keywords/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../response-property/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../.github/workflows/ci.yml | 48 +++++ .../server-sent-event-examples/.gitignore | 4 + .../.mock/definition/api.yml | 1 + .../.mock/definition/completions.yml | 36 ++++ .../.mock/fern.config.json | 1 + .../.mock/generators.yml | 1 + .../server-sent-event-examples/composer.json | 40 ++++ .../server-sent-event-examples/phpstan.neon | 5 + .../server-sent-event-examples/phpunit.xml | 7 + .../snippet-templates.json | 0 .../server-sent-event-examples/snippet.json | 0 .../src/Completions/CompletionsClient.php | 58 +++++ .../Requests/StreamCompletionRequest.php | 26 +++ .../Completions/Types/StreamedCompletion.php | 34 +++ .../src/Core/ArrayType.php | 16 ++ .../src/Core/BaseApiRequest.php | 22 ++ .../src/Core/Constant.php | 12 ++ .../src/Core/DateType.php | 16 ++ .../src/Core/HttpMethod.php | 12 ++ .../src/Core/JsonApiRequest.php | 25 +++ .../src/Core/JsonDecoder.php | 160 ++++++++++++++ .../src/Core/JsonDeserializer.php | 202 ++++++++++++++++++ .../src/Core/JsonEncoder.php | 20 ++ .../src/Core/JsonProperty.php | 13 ++ .../src/Core/JsonSerializer.php | 190 ++++++++++++++++ .../src/Core/RawClient.php | 138 ++++++++++++ .../src/Core/SerializableType.php | 179 ++++++++++++++++ .../src/Core/Union.php | 62 ++++++ .../src/Core/Utils.php | 61 ++++++ .../src/Exceptions/SeedApiException.php | 53 +++++ .../src/Exceptions/SeedException.php | 12 ++ .../src/SeedClient.php | 58 +++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/EnumTest.php | 76 +++++++ .../tests/Seed/Core/InvalidTypesTest.php | 45 ++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ++++++ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 +++++++++ .../tests/Seed/Core/NullPropertyTypeTest.php | 50 +++++ .../tests/Seed/Core/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/TestTypeTest.php | 201 +++++++++++++++++ .../tests/Seed/Core/UnionArrayTypeTest.php | 56 +++++ .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../.github/workflows/ci.yml | 48 +++++ seed/php-sdk/server-sent-events/.gitignore | 4 + .../.mock/definition/api.yml | 1 + .../.mock/definition/completions.yml | 22 ++ .../server-sent-events/.mock/fern.config.json | 1 + .../server-sent-events/.mock/generators.yml | 1 + seed/php-sdk/server-sent-events/composer.json | 40 ++++ seed/php-sdk/server-sent-events/phpstan.neon | 5 + seed/php-sdk/server-sent-events/phpunit.xml | 7 + .../server-sent-events/snippet-templates.json | 0 seed/php-sdk/server-sent-events/snippet.json | 0 .../src/Completions/CompletionsClient.php | 58 +++++ .../Requests/StreamCompletionRequest.php | 26 +++ .../Completions/Types/StreamedCompletion.php | 34 +++ .../server-sent-events/src/Core/ArrayType.php | 16 ++ .../src/Core/BaseApiRequest.php | 22 ++ .../server-sent-events/src/Core/Constant.php | 12 ++ .../server-sent-events/src/Core/DateType.php | 16 ++ .../src/Core/HttpMethod.php | 12 ++ .../src/Core/JsonApiRequest.php | 25 +++ .../src/Core/JsonDecoder.php | 160 ++++++++++++++ .../src/Core/JsonDeserializer.php | 202 ++++++++++++++++++ .../src/Core/JsonEncoder.php | 20 ++ .../src/Core/JsonProperty.php | 13 ++ .../src/Core/JsonSerializer.php | 190 ++++++++++++++++ .../server-sent-events/src/Core/RawClient.php | 138 ++++++++++++ .../src/Core/SerializableType.php | 179 ++++++++++++++++ .../server-sent-events/src/Core/Union.php | 62 ++++++ .../server-sent-events/src/Core/Utils.php | 61 ++++++ .../src/Exceptions/SeedApiException.php | 53 +++++ .../src/Exceptions/SeedException.php | 12 ++ .../server-sent-events/src/SeedClient.php | 58 +++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/EnumTest.php | 76 +++++++ .../tests/Seed/Core/InvalidTypesTest.php | 45 ++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ++++++ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 +++++++++ .../tests/Seed/Core/NullPropertyTypeTest.php | 50 +++++ .../tests/Seed/Core/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/TestTypeTest.php | 201 +++++++++++++++++ .../tests/Seed/Core/UnionArrayTypeTest.php | 56 +++++ .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../simple-fhir/src/Core/JsonDecoder.php | 13 ++ .../simple-fhir/src/Core/JsonDeserializer.php | 37 +++- .../simple-fhir/src/Core/JsonSerializer.php | 33 ++- .../simple-fhir/src/Core/SerializableType.php | 14 ++ seed/php-sdk/simple-fhir/src/Core/Union.php | 49 ++++- .../simple-fhir/src/Types/BaseResource.php | 7 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../streaming-parameter/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../streaming/src/Core/JsonDecoder.php | 13 ++ .../streaming/src/Core/JsonDeserializer.php | 37 +++- .../streaming/src/Core/JsonSerializer.php | 33 ++- .../streaming/src/Core/SerializableType.php | 14 ++ seed/php-sdk/streaming/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/trace/src/Core/JsonDecoder.php | 13 ++ .../trace/src/Core/JsonDeserializer.php | 37 +++- .../php-sdk/trace/src/Core/JsonSerializer.php | 33 ++- .../trace/src/Core/SerializableType.php | 14 ++ seed/php-sdk/trace/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../undiscriminated-unions/src/Core/Union.php | 49 ++++- .../src/Union/UnionClient.php | 17 +- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/unions/src/Core/JsonDecoder.php | 13 ++ .../unions/src/Core/JsonDeserializer.php | 37 +++- .../unions/src/Core/JsonSerializer.php | 33 ++- .../unions/src/Core/SerializableType.php | 14 ++ seed/php-sdk/unions/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/unknown/src/Core/JsonDecoder.php | 13 ++ .../unknown/src/Core/JsonDeserializer.php | 37 +++- .../unknown/src/Core/JsonSerializer.php | 33 ++- .../unknown/src/Core/SerializableType.php | 14 ++ seed/php-sdk/unknown/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../validation/src/Core/JsonDecoder.php | 13 ++ .../validation/src/Core/JsonDeserializer.php | 37 +++- .../validation/src/Core/JsonSerializer.php | 33 ++- .../validation/src/Core/SerializableType.php | 14 ++ seed/php-sdk/validation/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../variables/src/Core/JsonDecoder.php | 13 ++ .../variables/src/Core/JsonDeserializer.php | 37 +++- .../variables/src/Core/JsonSerializer.php | 33 ++- .../variables/src/Core/SerializableType.php | 14 ++ seed/php-sdk/variables/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../src/Core/JsonDecoder.php | 13 ++ .../src/Core/JsonDeserializer.php | 37 +++- .../src/Core/JsonSerializer.php | 33 ++- .../src/Core/SerializableType.php | 14 ++ .../version-no-default/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ seed/php-sdk/version/src/Core/JsonDecoder.php | 13 ++ .../version/src/Core/JsonDeserializer.php | 37 +++- .../version/src/Core/JsonSerializer.php | 33 ++- .../version/src/Core/SerializableType.php | 14 ++ seed/php-sdk/version/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ .../websocket/src/Core/JsonDecoder.php | 13 ++ .../websocket/src/Core/JsonDeserializer.php | 37 +++- .../websocket/src/Core/JsonSerializer.php | 33 ++- .../websocket/src/Core/SerializableType.php | 14 ++ seed/php-sdk/websocket/src/Core/Union.php | 49 ++++- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ++++++++++ 922 files changed, 38719 insertions(+), 2944 deletions(-) create mode 100644 generators/php/codegen/src/asIs/UnionPropertyTypeTest.Template.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/.github/workflows/ci.yml create mode 100644 seed/php-model/server-sent-event-examples/.gitignore create mode 100644 seed/php-model/server-sent-event-examples/.mock/definition/api.yml create mode 100644 seed/php-model/server-sent-event-examples/.mock/definition/completions.yml create mode 100644 seed/php-model/server-sent-event-examples/.mock/fern.config.json create mode 100644 seed/php-model/server-sent-event-examples/.mock/generators.yml create mode 100644 seed/php-model/server-sent-event-examples/composer.json create mode 100644 seed/php-model/server-sent-event-examples/phpstan.neon create mode 100644 seed/php-model/server-sent-event-examples/phpunit.xml create mode 100644 seed/php-model/server-sent-event-examples/snippet-templates.json create mode 100644 seed/php-model/server-sent-event-examples/snippet.json create mode 100644 seed/php-model/server-sent-event-examples/src/Completions/StreamedCompletion.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/ArrayType.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Constant.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/DateType.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonDecoder.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonDeserializer.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonEncoder.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonProperty.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonSerializer.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/SerializableType.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Union.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Utils.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/EnumTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/server-sent-events/.github/workflows/ci.yml create mode 100644 seed/php-model/server-sent-events/.gitignore create mode 100644 seed/php-model/server-sent-events/.mock/definition/api.yml create mode 100644 seed/php-model/server-sent-events/.mock/definition/completions.yml create mode 100644 seed/php-model/server-sent-events/.mock/fern.config.json create mode 100644 seed/php-model/server-sent-events/.mock/generators.yml create mode 100644 seed/php-model/server-sent-events/composer.json create mode 100644 seed/php-model/server-sent-events/phpstan.neon create mode 100644 seed/php-model/server-sent-events/phpunit.xml create mode 100644 seed/php-model/server-sent-events/snippet-templates.json create mode 100644 seed/php-model/server-sent-events/snippet.json create mode 100644 seed/php-model/server-sent-events/src/Completions/StreamedCompletion.php create mode 100644 seed/php-model/server-sent-events/src/Core/ArrayType.php create mode 100644 seed/php-model/server-sent-events/src/Core/Constant.php create mode 100644 seed/php-model/server-sent-events/src/Core/DateType.php create mode 100644 seed/php-model/server-sent-events/src/Core/JsonDecoder.php create mode 100644 seed/php-model/server-sent-events/src/Core/JsonDeserializer.php create mode 100644 seed/php-model/server-sent-events/src/Core/JsonEncoder.php create mode 100644 seed/php-model/server-sent-events/src/Core/JsonProperty.php create mode 100644 seed/php-model/server-sent-events/src/Core/JsonSerializer.php create mode 100644 seed/php-model/server-sent-events/src/Core/SerializableType.php create mode 100644 seed/php-model/server-sent-events/src/Core/Union.php create mode 100644 seed/php-model/server-sent-events/src/Core/Utils.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/EmptyArraysTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/EnumTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/ScalarTypesTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/TestTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/.github/workflows/ci.yml create mode 100644 seed/php-sdk/server-sent-event-examples/.gitignore create mode 100644 seed/php-sdk/server-sent-event-examples/.mock/definition/api.yml create mode 100644 seed/php-sdk/server-sent-event-examples/.mock/definition/completions.yml create mode 100644 seed/php-sdk/server-sent-event-examples/.mock/fern.config.json create mode 100644 seed/php-sdk/server-sent-event-examples/.mock/generators.yml create mode 100644 seed/php-sdk/server-sent-event-examples/composer.json create mode 100644 seed/php-sdk/server-sent-event-examples/phpstan.neon create mode 100644 seed/php-sdk/server-sent-event-examples/phpunit.xml create mode 100644 seed/php-sdk/server-sent-event-examples/snippet-templates.json create mode 100644 seed/php-sdk/server-sent-event-examples/snippet.json create mode 100644 seed/php-sdk/server-sent-event-examples/src/Completions/CompletionsClient.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Completions/Requests/StreamCompletionRequest.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Completions/Types/StreamedCompletion.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/ArrayType.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Constant.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/DateType.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonApiRequest.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonDecoder.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonDeserializer.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonEncoder.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonProperty.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonSerializer.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/RawClient.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/SerializableType.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Union.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Utils.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Exceptions/SeedApiException.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Exceptions/SeedException.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/SeedClient.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EnumTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/RawClientTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/.github/workflows/ci.yml create mode 100644 seed/php-sdk/server-sent-events/.gitignore create mode 100644 seed/php-sdk/server-sent-events/.mock/definition/api.yml create mode 100644 seed/php-sdk/server-sent-events/.mock/definition/completions.yml create mode 100644 seed/php-sdk/server-sent-events/.mock/fern.config.json create mode 100644 seed/php-sdk/server-sent-events/.mock/generators.yml create mode 100644 seed/php-sdk/server-sent-events/composer.json create mode 100644 seed/php-sdk/server-sent-events/phpstan.neon create mode 100644 seed/php-sdk/server-sent-events/phpunit.xml create mode 100644 seed/php-sdk/server-sent-events/snippet-templates.json create mode 100644 seed/php-sdk/server-sent-events/snippet.json create mode 100644 seed/php-sdk/server-sent-events/src/Completions/CompletionsClient.php create mode 100644 seed/php-sdk/server-sent-events/src/Completions/Requests/StreamCompletionRequest.php create mode 100644 seed/php-sdk/server-sent-events/src/Completions/Types/StreamedCompletion.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/ArrayType.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Constant.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/DateType.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonApiRequest.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonDecoder.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonDeserializer.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonEncoder.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonProperty.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonSerializer.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/RawClient.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/SerializableType.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Union.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Utils.php create mode 100644 seed/php-sdk/server-sent-events/src/Exceptions/SeedApiException.php create mode 100644 seed/php-sdk/server-sent-events/src/Exceptions/SeedException.php create mode 100644 seed/php-sdk/server-sent-events/src/SeedClient.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/EmptyArraysTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/EnumTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/RawClientTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/ScalarTypesTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/TestTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/UnionPropertyTypeTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/UnionPropertyTypeTest.php diff --git a/generators/php/codegen/src/AsIs.ts b/generators/php/codegen/src/AsIs.ts index 3a496d31d80..afe44fb2275 100644 --- a/generators/php/codegen/src/AsIs.ts +++ b/generators/php/codegen/src/AsIs.ts @@ -16,6 +16,7 @@ export enum AsIsFiles { NestedUnionArrayTypeTest = "NestedUnionArrayTypeTest.Template.php", NullableArrayTypeTest = "NullableArrayTypeTest.Template.php", NullPropertyTypeTest = "NullPropertyTypeTest.Template.php", + UnionPropertyTypeTest = "UnionPropertyTypeTest.Template.php", ScalarTypesTest = "ScalarTypesTest.Template.php", EnumTest = "EnumTest.Template.php", TestTypeTest = "TestTypeTest.Template.php", diff --git a/generators/php/codegen/src/asIs/JsonDecoder.Template.php b/generators/php/codegen/src/asIs/JsonDecoder.Template.php index cc4238be49a..e34af48812a 100644 --- a/generators/php/codegen/src/asIs/JsonDecoder.Template.php +++ b/generators/php/codegen/src/asIs/JsonDecoder.Template.php @@ -15,7 +15,8 @@ class JsonDecoder * @return string The decoded string. * @throws JsonException If the decoded value is not a string. */ - public static function decodeString(string $json): string { + public static function decodeString(string $json): string + { $decoded = self::decode($json); if (!is_string($decoded)) { throw new JsonException("Unexpected non-string json value: " . $json); @@ -30,7 +31,8 @@ public static function decodeString(string $json): string { * @return bool The decoded boolean. * @throws JsonException If the decoded value is not a boolean. */ - public static function decodeBool(string $json): bool { + public static function decodeBool(string $json): bool + { $decoded = self::decode($json); if (!is_bool($decoded)) { throw new JsonException("Unexpected non-boolean json value: " . $json); @@ -45,7 +47,8 @@ public static function decodeBool(string $json): bool { * @return DateTime The decoded DateTime object. * @throws JsonException If the decoded value is not a valid datetime string. */ - public static function decodeDateTime(string $json): DateTime { + public static function decodeDateTime(string $json): DateTime + { $decoded = self::decode($json); if (!is_string($decoded)) { throw new JsonException("Unexpected non-string json value for datetime: " . $json); @@ -60,7 +63,8 @@ public static function decodeDateTime(string $json): DateTime { * @return DateTime The decoded DateTime object. * @throws JsonException If the decoded value is not a valid date string. */ - public static function decodeDate(string $json): DateTime { + public static function decodeDate(string $json): DateTime + { $decoded = self::decode($json); if (!is_string($decoded)) { throw new JsonException("Unexpected non-string json value for date: " . $json); @@ -75,7 +79,8 @@ public static function decodeDate(string $json): DateTime { * @return float The decoded float. * @throws JsonException If the decoded value is not a float. */ - public static function decodeFloat(string $json): float { + public static function decodeFloat(string $json): float + { $decoded = self::decode($json); if (!is_float($decoded)) { throw new JsonException("Unexpected non-float json value: " . $json); @@ -90,7 +95,8 @@ public static function decodeFloat(string $json): float { * @return int The decoded integer. * @throws JsonException If the decoded value is not an integer. */ - public static function decodeInt(string $json): int { + public static function decodeInt(string $json): int + { $decoded = self::decode($json); if (!is_int($decoded)) { throw new JsonException("Unexpected non-integer json value: " . $json); @@ -106,7 +112,8 @@ public static function decodeInt(string $json): int { * @return mixed[]|array The deserialized array. * @throws JsonException If the decoded value is not an array. */ - public static function decodeArray(string $json, array $type): array { + public static function decodeArray(string $json, array $type): array + { $decoded = self::decode($json); if (!is_array($decoded)) { throw new JsonException("Unexpected non-array json value: " . $json); @@ -114,6 +121,19 @@ public static function decodeArray(string $json, array $type): array { return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * @@ -121,7 +141,8 @@ public static function decodeArray(string $json, array $type): array { * @return mixed The decoded mixed. * @throws JsonException If the decoded value is not an mixed. */ - public static function decodeMixed(string $json): mixed { + public static function decodeMixed(string $json): mixed + { return self::decode($json); } @@ -132,7 +153,8 @@ public static function decodeMixed(string $json): mixed { * @return mixed The decoded value. * @throws JsonException If an error occurs during JSON decoding. */ - public static function decode(string $json): mixed { + public static function decode(string $json): mixed + { return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); } -} \ No newline at end of file +} diff --git a/generators/php/codegen/src/asIs/JsonDeserializer.Template.php b/generators/php/codegen/src/asIs/JsonDeserializer.Template.php index 987e8164672..f0a42290111 100644 --- a/generators/php/codegen/src/asIs/JsonDeserializer.Template.php +++ b/generators/php/codegen/src/asIs/JsonDeserializer.Template.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/generators/php/codegen/src/asIs/JsonSerializer.Template.php b/generators/php/codegen/src/asIs/JsonSerializer.Template.php index 27ce301c901..bd046196807 100644 --- a/generators/php/codegen/src/asIs/JsonSerializer.Template.php +++ b/generators/php/codegen/src/asIs/JsonSerializer.Template.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/generators/php/codegen/src/asIs/SerializableType.Template.php b/generators/php/codegen/src/asIs/SerializableType.Template.php index 39acdc2b8ca..6abe274c4c2 100644 --- a/generators/php/codegen/src/asIs/SerializableType.Template.php +++ b/generators/php/codegen/src/asIs/SerializableType.Template.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { @@ -162,4 +176,4 @@ private static function getJsonKey(ReflectionProperty $property): ?string $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; return $jsonPropertyAttr?->newInstance()?->name; } -} \ No newline at end of file +} diff --git a/generators/php/codegen/src/asIs/Union.Template.php b/generators/php/codegen/src/asIs/Union.Template.php index 891e1788f98..78aa129b4cc 100644 --- a/generators/php/codegen/src/asIs/Union.Template.php +++ b/generators/php/codegen/src/asIs/Union.Template.php @@ -2,20 +2,61 @@ namespace <%= coreNamespace%>; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } - public function __toString(): string { - return implode(' | ', $this->types); + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } -} - \ No newline at end of file +} \ No newline at end of file diff --git a/generators/php/codegen/src/asIs/UnionPropertyTypeTest.Template.php b/generators/php/codegen/src/asIs/UnionPropertyTypeTest.Template.php new file mode 100644 index 00000000000..4377a6fee9b --- /dev/null +++ b/generators/php/codegen/src/asIs/UnionPropertyTypeTest.Template.php @@ -0,0 +1,118 @@ +; + +use PHPUnit\Framework\TestCase; +use <%= coreNamespace%>\JsonProperty; +use <%= coreNamespace%>\SerializableType; +use <%= coreNamespace%>\Union; + +class UnionPropertyType extends SerializableType +{ + + #[Union(new Union('string', 'integer'), 'null', ['integer' => 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) + { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} \ No newline at end of file diff --git a/generators/php/codegen/src/ast/Attribute.ts b/generators/php/codegen/src/ast/Attribute.ts index 102dc26bd02..5b5d1b45d5a 100644 --- a/generators/php/codegen/src/ast/Attribute.ts +++ b/generators/php/codegen/src/ast/Attribute.ts @@ -26,13 +26,16 @@ export class Attribute extends AstNode { writer.write(`${this.reference.name}`); if (this.arguments.length > 0) { writer.write("("); - for (const argument of this.arguments) { + this.arguments.forEach((argument, index) => { + if (index > 0) { + writer.write(","); + } if (typeof argument === "string") { writer.write(argument); } else { argument.write(writer); } - } + }); writer.write(")"); } } diff --git a/generators/php/codegen/src/ast/Type.ts b/generators/php/codegen/src/ast/Type.ts index 64e8866d992..d2f553c0e46 100644 --- a/generators/php/codegen/src/ast/Type.ts +++ b/generators/php/codegen/src/ast/Type.ts @@ -176,14 +176,17 @@ export class Type extends AstNode { writer.write("}"); break; } - case "union": - this.internalType.types.forEach((type, index) => { + case "union": { + const types = this.getUniqueTypes({ types: this.internalType.types, comment, writer }); + types.forEach((type, index) => { if (index > 0) { writer.write("|"); } - type.write(writer); + type.write(writer, { comment }); + index++; }); break; + } case "optional": { const isUnion = this.internalType.value.internalType.type === "union"; if (!isUnion) { @@ -358,6 +361,34 @@ export class Type extends AstNode { writer.write(": "); entry.valueType.write(writer, { comment }); } + + private getUniqueTypes({ + writer, + types, + comment + }: { + writer: Writer; + types: Type[]; + comment: boolean | undefined; + }): Type[] { + const typeStrings = new Set(); + return types.filter((type) => { + if (comment) { + return true; + } + const typeString = type.toString({ + namespace: writer.namespace, + rootNamespace: writer.rootNamespace, + customConfig: writer.customConfig + }); + // handle potential duplicates, such as strings (due to enums) and arrays + if (typeStrings.has(typeString)) { + return false; + } + typeStrings.add(typeString); + return true; + }); + } } export const DateTimeClassReference = new ClassReference({ diff --git a/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts b/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts index b47f7043383..d8678d182d1 100644 --- a/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts +++ b/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts @@ -296,7 +296,8 @@ export abstract class AbstractPhpGeneratorContext< AsIsFiles.ScalarTypesTest, AsIsFiles.TestTypeTest, AsIsFiles.UnionArrayTypeTest, - AsIsFiles.EnumTest + AsIsFiles.EnumTest, + AsIsFiles.UnionPropertyTypeTest ]; } diff --git a/generators/php/codegen/src/context/PhpAttributeMapper.ts b/generators/php/codegen/src/context/PhpAttributeMapper.ts index 750840c7b83..32abf78cb48 100644 --- a/generators/php/codegen/src/context/PhpAttributeMapper.ts +++ b/generators/php/codegen/src/context/PhpAttributeMapper.ts @@ -1,7 +1,11 @@ import { assertNever } from "@fern-api/core-utils"; +import { Arguments, UnnamedArgument } from "@fern-api/generator-commons"; import { ObjectProperty } from "@fern-fern/ir-sdk/api"; +import { isEqual, uniq, uniqWith } from "lodash-es"; import { php } from ".."; +import { ClassInstantiation } from "../ast"; import { BasePhpCustomConfigSchema } from "../custom-config/BasePhpCustomConfigSchema"; +import { parameter } from "../php"; import { AbstractPhpGeneratorContext } from "./AbstractPhpGeneratorContext"; export declare namespace PhpAttributeMapper { @@ -41,14 +45,41 @@ export class PhpAttributeMapper { attributes.push( php.attribute({ reference: this.context.getArrayTypeClassReference(), - arguments: [this.getArrayTypeAttributeArgument(type.underlyingType())] + arguments: [this.getTypeAttributeArgument(type.underlyingType())] }) ); } + if (underlyingInternalType.type === "union") { + const unionTypeParameters = this.getUnionTypeParameters(underlyingInternalType.types); + // only add the attribute if deduping in getUnionTypeParameters resulted in more than one type + if (unionTypeParameters.length > 1) { + attributes.push( + php.attribute({ + reference: this.context.getUnionClassReference(), + arguments: this.getUnionTypeParameters(underlyingInternalType.types) + }) + ); + } + } return attributes; } - public getArrayTypeAttributeArgument(type: php.Type): php.AstNode { + public getUnionTypeClassRepresentation(arguments_: php.AstNode[]): ClassInstantiation { + return php.instantiateClass({ + classReference: this.context.getUnionClassReference(), + arguments_ + }); + } + + public getUnionTypeParameters(types: php.Type[]): php.AstNode[] { + // remove duplicates, such as "string" and "string" if enums and strings are both in the union + return uniqWith( + types.map((type) => this.getTypeAttributeArgument(type)), + isEqual + ); + } + + public getTypeAttributeArgument(type: php.Type): php.AstNode { switch (type.internalType.type) { case "int": return php.codeblock("'integer'"); @@ -69,14 +100,14 @@ export class PhpAttributeMapper { return php.codeblock("'object'"); case "array": return php.array({ - entries: [this.getArrayTypeAttributeArgument(type.internalType.value)] + entries: [this.getTypeAttributeArgument(type.internalType.value)] }); case "map": { return php.map({ entries: [ { - key: this.getArrayTypeAttributeArgument(type.internalType.keyType), - value: this.getArrayTypeAttributeArgument(type.internalType.valueType) + key: this.getTypeAttributeArgument(type.internalType.keyType), + value: this.getTypeAttributeArgument(type.internalType.valueType) } ] }); @@ -92,17 +123,19 @@ export class PhpAttributeMapper { }); } case "union": { - return php.instantiateClass({ - classReference: this.context.getUnionClassReference(), - arguments_: type.internalType.types.map((unionType) => - this.getArrayTypeAttributeArgument(unionType) - ) - }); + const unionTypeParameters = this.getUnionTypeParameters(type.internalType.types); + if (unionTypeParameters.length === 1) { + if (unionTypeParameters[0] == null) { + throw new Error("Unexpected empty union type parameters"); + } + return unionTypeParameters[0]; + } + return this.getUnionTypeClassRepresentation(unionTypeParameters); } case "optional": return php.instantiateClass({ classReference: this.context.getUnionClassReference(), - arguments_: [this.getArrayTypeAttributeArgument(type.internalType.value), php.codeblock("'null'")] + arguments_: [this.getTypeAttributeArgument(type.internalType.value), php.codeblock("'null'")] }); case "reference": { const reference = type.internalType.value; diff --git a/generators/php/codegen/src/context/PhpTypeMapper.ts b/generators/php/codegen/src/context/PhpTypeMapper.ts index cddb80ddea5..305d5836886 100644 --- a/generators/php/codegen/src/context/PhpTypeMapper.ts +++ b/generators/php/codegen/src/context/PhpTypeMapper.ts @@ -9,6 +9,7 @@ import { TypeId, TypeReference } from "@fern-fern/ir-sdk/api"; +import { isEqual, uniqWith } from "lodash-es"; import { php } from "../"; import { ClassReference, Type } from "../ast"; import { BasePhpCustomConfigSchema } from "../custom-config/BasePhpCustomConfigSchema"; @@ -118,7 +119,15 @@ export class PhpTypeMapper { case "union": return php.Type.mixed(); case "undiscriminatedUnion": { - return php.Type.mixed(); + return php.Type.union( + // need to dedupe because lists and sets are both represented as array + uniqWith( + typeDeclaration.shape.members.map((member) => + this.convert({ reference: member.type, preserveEnums }) + ), + isEqual + ) + ); } default: assertNever(typeDeclaration.shape); diff --git a/generators/php/sdk/src/endpoint/http/HttpEndpointGenerator.ts b/generators/php/sdk/src/endpoint/http/HttpEndpointGenerator.ts index 065b51fb500..43629e63c96 100644 --- a/generators/php/sdk/src/endpoint/http/HttpEndpointGenerator.ts +++ b/generators/php/sdk/src/endpoint/http/HttpEndpointGenerator.ts @@ -221,6 +221,10 @@ export class HttpEndpointGenerator extends AbstractEndpointGenerator { methodSuffix: "String" }); case "union": + return this.decodeJsonResponseForUnion({ + arguments_, + types: internalType.types + }); case "object": case "optional": case "typeDict": @@ -259,7 +263,7 @@ export class HttpEndpointGenerator extends AbstractEndpointGenerator { php.invokeMethod({ on: this.context.getJsonDecoderClassReference(), method: "decodeArray", - arguments_: [...arguments_, this.context.phpAttributeMapper.getArrayTypeAttributeArgument(type)], + arguments_: [...arguments_, this.context.phpAttributeMapper.getTypeAttributeArgument(type)], static_: true }) ); @@ -291,6 +295,34 @@ export class HttpEndpointGenerator extends AbstractEndpointGenerator { }); } + private decodeJsonResponseForUnion({ + arguments_, + types + }: { + arguments_: UnnamedArgument[]; + types: php.Type[]; + }): php.CodeBlock { + const unionTypeParameters = this.context.phpAttributeMapper.getUnionTypeParameters(types); + // if deduping in getUnionTypeParameters results in one type, treat it like just that type + if (unionTypeParameters.length === 1) { + return this.decodeJsonResponse(types[0]); + } + return php.codeblock((writer) => { + writer.writeNode( + php.invokeMethod({ + on: this.context.getJsonDecoderClassReference(), + method: "decodeUnion", + arguments_: [ + ...arguments_, + this.context.phpAttributeMapper.getUnionTypeClassRepresentation(unionTypeParameters) + ], + static_: true + }) + ); + writer.writeLine("; // @phpstan-ignore-line"); + }); + } + private getResponseBodyContent(): php.CodeBlock { return php.codeblock((writer) => { writer.write(`${JSON_VARIABLE_NAME} = ${RESPONSE_VARIABLE_NAME}->getBody()->getContents()`); diff --git a/generators/php/sdk/src/endpoint/request/EndpointRequest.ts b/generators/php/sdk/src/endpoint/request/EndpointRequest.ts index 39997730a2a..8c6d8a983e3 100644 --- a/generators/php/sdk/src/endpoint/request/EndpointRequest.ts +++ b/generators/php/sdk/src/endpoint/request/EndpointRequest.ts @@ -39,8 +39,23 @@ export abstract class EndpointRequest { protected serializeJsonRequest({ bodyArgument }: { bodyArgument: php.CodeBlock }): php.CodeBlock { const requestParameterType = this.getRequestParameterType(); - const isOptional = requestParameterType.isOptional(); - const underlyingType = this.getRequestParameterType().underlyingType(); + return this.serializeJsonType({ + type: requestParameterType, + bodyArgument, + isOptional: requestParameterType.isOptional() + }); + } + + protected serializeJsonType({ + type, + bodyArgument, + isOptional + }: { + type: php.Type; + bodyArgument: php.CodeBlock; + isOptional: boolean; + }): php.CodeBlock { + const underlyingType = type.underlyingType(); const internalType = underlyingType.internalType; switch (internalType.type) { case "array": @@ -62,6 +77,12 @@ export abstract class EndpointRequest { variant: "DateTime", isOptional }); + case "union": + return this.serializeJsonForUnion({ + bodyArgument, + types: internalType.types, + isOptional + }); case "reference": case "int": case "float": @@ -72,7 +93,6 @@ export abstract class EndpointRequest { case "optional": case "typeDict": case "enumString": - case "union": return bodyArgument; } } @@ -91,7 +111,39 @@ export abstract class EndpointRequest { methodInvocation: php.invokeMethod({ on: this.context.getJsonSerializerClassReference(), method: "serializeArray", - arguments_: [bodyArgument, this.context.phpAttributeMapper.getArrayTypeAttributeArgument(type)], + arguments_: [bodyArgument, this.context.phpAttributeMapper.getTypeAttributeArgument(type)], + static_: true + }), + isOptional + }); + } + + protected serializeJsonForUnion({ + bodyArgument, + types, + isOptional + }: { + bodyArgument: php.CodeBlock; + types: php.Type[]; + isOptional: boolean; + }): php.CodeBlock { + const unionTypeParameters = this.context.phpAttributeMapper.getUnionTypeParameters(types); + // if deduping in getUnionTypeParameters results in one type, treat it like just that type + if (unionTypeParameters.length === 1) { + if (types[0] == null) { + throw new Error("Unexpected empty types"); + } + return this.serializeJsonType({ type: types[0], bodyArgument, isOptional }); + } + return this.serializeJsonRequestMethod({ + bodyArgument, + methodInvocation: php.invokeMethod({ + on: this.context.getJsonSerializerClassReference(), + method: "serializeUnion", + arguments_: [ + bodyArgument, + this.context.phpAttributeMapper.getUnionTypeClassRepresentation(unionTypeParameters) + ], static_: true }), isOptional diff --git a/generators/php/sdk/src/endpoint/request/WrappedEndpointRequest.ts b/generators/php/sdk/src/endpoint/request/WrappedEndpointRequest.ts index e4538231e86..2560506b6e8 100644 --- a/generators/php/sdk/src/endpoint/request/WrappedEndpointRequest.ts +++ b/generators/php/sdk/src/endpoint/request/WrappedEndpointRequest.ts @@ -140,7 +140,16 @@ export class WrappedEndpointRequest extends EndpointRequest { if (maybeLiteral != null) { return php.codeblock(this.context.getLiteralAsString(maybeLiteral)); } - return php.codeblock(`${parameter}`); + const type = this.context.phpTypeMapper.convert({ reference }); + const underlyingInternalType = type.underlyingType().internalType; + if (underlyingInternalType.type === "union") { + return this.serializeJsonForUnion({ + bodyArgument: php.codeblock(parameter), + types: underlyingInternalType.types, + isOptional: false + }); + } + return php.codeblock(parameter); } public getRequestBodyCodeBlock(): RequestBodyCodeBlock | undefined { diff --git a/generators/php/sdk/versions.yml b/generators/php/sdk/versions.yml index 8fd359a75df..c9a63e645a9 100644 --- a/generators/php/sdk/versions.yml +++ b/generators/php/sdk/versions.yml @@ -1,3 +1,8 @@ +- version: 0.1.5 + changelogEntry: + - type: feat + summary: >- + Support undiscriminated unions. - version: 0.1.4 changelogEntry: - type: fix diff --git a/seed/php-model/alias-extends/src/Core/JsonDecoder.php b/seed/php-model/alias-extends/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/alias-extends/src/Core/JsonDecoder.php +++ b/seed/php-model/alias-extends/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/alias-extends/src/Core/JsonDeserializer.php b/seed/php-model/alias-extends/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/alias-extends/src/Core/JsonDeserializer.php +++ b/seed/php-model/alias-extends/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/alias-extends/src/Core/JsonSerializer.php b/seed/php-model/alias-extends/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/alias-extends/src/Core/JsonSerializer.php +++ b/seed/php-model/alias-extends/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/alias-extends/src/Core/SerializableType.php b/seed/php-model/alias-extends/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/alias-extends/src/Core/SerializableType.php +++ b/seed/php-model/alias-extends/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/alias-extends/src/Core/Union.php b/seed/php-model/alias-extends/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/alias-extends/src/Core/Union.php +++ b/seed/php-model/alias-extends/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/alias/src/Core/JsonDecoder.php b/seed/php-model/alias/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/alias/src/Core/JsonDecoder.php +++ b/seed/php-model/alias/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/alias/src/Core/JsonDeserializer.php b/seed/php-model/alias/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/alias/src/Core/JsonDeserializer.php +++ b/seed/php-model/alias/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/alias/src/Core/JsonSerializer.php b/seed/php-model/alias/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/alias/src/Core/JsonSerializer.php +++ b/seed/php-model/alias/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/alias/src/Core/SerializableType.php b/seed/php-model/alias/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/alias/src/Core/SerializableType.php +++ b/seed/php-model/alias/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/alias/src/Core/Union.php b/seed/php-model/alias/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/alias/src/Core/Union.php +++ b/seed/php-model/alias/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/alias/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/alias/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/any-auth/src/Core/JsonDecoder.php b/seed/php-model/any-auth/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/any-auth/src/Core/JsonDecoder.php +++ b/seed/php-model/any-auth/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/any-auth/src/Core/JsonDeserializer.php b/seed/php-model/any-auth/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/any-auth/src/Core/JsonDeserializer.php +++ b/seed/php-model/any-auth/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/any-auth/src/Core/JsonSerializer.php b/seed/php-model/any-auth/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/any-auth/src/Core/JsonSerializer.php +++ b/seed/php-model/any-auth/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/any-auth/src/Core/SerializableType.php b/seed/php-model/any-auth/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/any-auth/src/Core/SerializableType.php +++ b/seed/php-model/any-auth/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/any-auth/src/Core/Union.php b/seed/php-model/any-auth/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/any-auth/src/Core/Union.php +++ b/seed/php-model/any-auth/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/api-wide-base-path/src/Core/JsonDecoder.php b/seed/php-model/api-wide-base-path/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/api-wide-base-path/src/Core/JsonDecoder.php +++ b/seed/php-model/api-wide-base-path/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/api-wide-base-path/src/Core/JsonDeserializer.php b/seed/php-model/api-wide-base-path/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/api-wide-base-path/src/Core/JsonDeserializer.php +++ b/seed/php-model/api-wide-base-path/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/api-wide-base-path/src/Core/JsonSerializer.php b/seed/php-model/api-wide-base-path/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/api-wide-base-path/src/Core/JsonSerializer.php +++ b/seed/php-model/api-wide-base-path/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/api-wide-base-path/src/Core/SerializableType.php b/seed/php-model/api-wide-base-path/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/api-wide-base-path/src/Core/SerializableType.php +++ b/seed/php-model/api-wide-base-path/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/api-wide-base-path/src/Core/Union.php b/seed/php-model/api-wide-base-path/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/api-wide-base-path/src/Core/Union.php +++ b/seed/php-model/api-wide-base-path/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/audiences/src/Core/JsonDecoder.php b/seed/php-model/audiences/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/audiences/src/Core/JsonDecoder.php +++ b/seed/php-model/audiences/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/audiences/src/Core/JsonDeserializer.php b/seed/php-model/audiences/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/audiences/src/Core/JsonDeserializer.php +++ b/seed/php-model/audiences/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/audiences/src/Core/JsonSerializer.php b/seed/php-model/audiences/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/audiences/src/Core/JsonSerializer.php +++ b/seed/php-model/audiences/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/audiences/src/Core/SerializableType.php b/seed/php-model/audiences/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/audiences/src/Core/SerializableType.php +++ b/seed/php-model/audiences/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/audiences/src/Core/Union.php b/seed/php-model/audiences/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/audiences/src/Core/Union.php +++ b/seed/php-model/audiences/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/audiences/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/auth-environment-variables/src/Core/JsonDecoder.php b/seed/php-model/auth-environment-variables/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/auth-environment-variables/src/Core/JsonDecoder.php +++ b/seed/php-model/auth-environment-variables/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/auth-environment-variables/src/Core/JsonDeserializer.php b/seed/php-model/auth-environment-variables/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/auth-environment-variables/src/Core/JsonDeserializer.php +++ b/seed/php-model/auth-environment-variables/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/auth-environment-variables/src/Core/JsonSerializer.php b/seed/php-model/auth-environment-variables/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/auth-environment-variables/src/Core/JsonSerializer.php +++ b/seed/php-model/auth-environment-variables/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/auth-environment-variables/src/Core/SerializableType.php b/seed/php-model/auth-environment-variables/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/auth-environment-variables/src/Core/SerializableType.php +++ b/seed/php-model/auth-environment-variables/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/auth-environment-variables/src/Core/Union.php b/seed/php-model/auth-environment-variables/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/auth-environment-variables/src/Core/Union.php +++ b/seed/php-model/auth-environment-variables/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/JsonDecoder.php b/seed/php-model/basic-auth-environment-variables/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/basic-auth-environment-variables/src/Core/JsonDecoder.php +++ b/seed/php-model/basic-auth-environment-variables/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/JsonDeserializer.php b/seed/php-model/basic-auth-environment-variables/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/basic-auth-environment-variables/src/Core/JsonDeserializer.php +++ b/seed/php-model/basic-auth-environment-variables/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/JsonSerializer.php b/seed/php-model/basic-auth-environment-variables/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/basic-auth-environment-variables/src/Core/JsonSerializer.php +++ b/seed/php-model/basic-auth-environment-variables/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/SerializableType.php b/seed/php-model/basic-auth-environment-variables/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/basic-auth-environment-variables/src/Core/SerializableType.php +++ b/seed/php-model/basic-auth-environment-variables/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Union.php b/seed/php-model/basic-auth-environment-variables/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/basic-auth-environment-variables/src/Core/Union.php +++ b/seed/php-model/basic-auth-environment-variables/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/basic-auth/src/Core/JsonDecoder.php b/seed/php-model/basic-auth/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/basic-auth/src/Core/JsonDecoder.php +++ b/seed/php-model/basic-auth/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/basic-auth/src/Core/JsonDeserializer.php b/seed/php-model/basic-auth/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/basic-auth/src/Core/JsonDeserializer.php +++ b/seed/php-model/basic-auth/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/basic-auth/src/Core/JsonSerializer.php b/seed/php-model/basic-auth/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/basic-auth/src/Core/JsonSerializer.php +++ b/seed/php-model/basic-auth/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/basic-auth/src/Core/SerializableType.php b/seed/php-model/basic-auth/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/basic-auth/src/Core/SerializableType.php +++ b/seed/php-model/basic-auth/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/basic-auth/src/Core/Union.php b/seed/php-model/basic-auth/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/basic-auth/src/Core/Union.php +++ b/seed/php-model/basic-auth/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/JsonDecoder.php b/seed/php-model/bearer-token-environment-variable/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/bearer-token-environment-variable/src/Core/JsonDecoder.php +++ b/seed/php-model/bearer-token-environment-variable/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/JsonDeserializer.php b/seed/php-model/bearer-token-environment-variable/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/bearer-token-environment-variable/src/Core/JsonDeserializer.php +++ b/seed/php-model/bearer-token-environment-variable/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/JsonSerializer.php b/seed/php-model/bearer-token-environment-variable/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/bearer-token-environment-variable/src/Core/JsonSerializer.php +++ b/seed/php-model/bearer-token-environment-variable/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/SerializableType.php b/seed/php-model/bearer-token-environment-variable/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/bearer-token-environment-variable/src/Core/SerializableType.php +++ b/seed/php-model/bearer-token-environment-variable/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Union.php b/seed/php-model/bearer-token-environment-variable/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/bearer-token-environment-variable/src/Core/Union.php +++ b/seed/php-model/bearer-token-environment-variable/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/bytes/src/Core/JsonDecoder.php b/seed/php-model/bytes/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/bytes/src/Core/JsonDecoder.php +++ b/seed/php-model/bytes/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/bytes/src/Core/JsonDeserializer.php b/seed/php-model/bytes/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/bytes/src/Core/JsonDeserializer.php +++ b/seed/php-model/bytes/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/bytes/src/Core/JsonSerializer.php b/seed/php-model/bytes/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/bytes/src/Core/JsonSerializer.php +++ b/seed/php-model/bytes/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/bytes/src/Core/SerializableType.php b/seed/php-model/bytes/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/bytes/src/Core/SerializableType.php +++ b/seed/php-model/bytes/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/bytes/src/Core/Union.php b/seed/php-model/bytes/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/bytes/src/Core/Union.php +++ b/seed/php-model/bytes/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/bytes/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/circular-references-advanced/src/Core/JsonDecoder.php b/seed/php-model/circular-references-advanced/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/circular-references-advanced/src/Core/JsonDecoder.php +++ b/seed/php-model/circular-references-advanced/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/circular-references-advanced/src/Core/JsonDeserializer.php b/seed/php-model/circular-references-advanced/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/circular-references-advanced/src/Core/JsonDeserializer.php +++ b/seed/php-model/circular-references-advanced/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/circular-references-advanced/src/Core/JsonSerializer.php b/seed/php-model/circular-references-advanced/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/circular-references-advanced/src/Core/JsonSerializer.php +++ b/seed/php-model/circular-references-advanced/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/circular-references-advanced/src/Core/SerializableType.php b/seed/php-model/circular-references-advanced/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/circular-references-advanced/src/Core/SerializableType.php +++ b/seed/php-model/circular-references-advanced/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/circular-references-advanced/src/Core/Union.php b/seed/php-model/circular-references-advanced/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/circular-references-advanced/src/Core/Union.php +++ b/seed/php-model/circular-references-advanced/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/circular-references/src/Core/JsonDecoder.php b/seed/php-model/circular-references/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/circular-references/src/Core/JsonDecoder.php +++ b/seed/php-model/circular-references/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/circular-references/src/Core/JsonDeserializer.php b/seed/php-model/circular-references/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/circular-references/src/Core/JsonDeserializer.php +++ b/seed/php-model/circular-references/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/circular-references/src/Core/JsonSerializer.php b/seed/php-model/circular-references/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/circular-references/src/Core/JsonSerializer.php +++ b/seed/php-model/circular-references/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/circular-references/src/Core/SerializableType.php b/seed/php-model/circular-references/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/circular-references/src/Core/SerializableType.php +++ b/seed/php-model/circular-references/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/circular-references/src/Core/Union.php b/seed/php-model/circular-references/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/circular-references/src/Core/Union.php +++ b/seed/php-model/circular-references/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/cross-package-type-names/src/Core/JsonDecoder.php b/seed/php-model/cross-package-type-names/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/cross-package-type-names/src/Core/JsonDecoder.php +++ b/seed/php-model/cross-package-type-names/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/cross-package-type-names/src/Core/JsonDeserializer.php b/seed/php-model/cross-package-type-names/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/cross-package-type-names/src/Core/JsonDeserializer.php +++ b/seed/php-model/cross-package-type-names/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/cross-package-type-names/src/Core/JsonSerializer.php b/seed/php-model/cross-package-type-names/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/cross-package-type-names/src/Core/JsonSerializer.php +++ b/seed/php-model/cross-package-type-names/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/cross-package-type-names/src/Core/SerializableType.php b/seed/php-model/cross-package-type-names/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/cross-package-type-names/src/Core/SerializableType.php +++ b/seed/php-model/cross-package-type-names/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/cross-package-type-names/src/Core/Union.php b/seed/php-model/cross-package-type-names/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/cross-package-type-names/src/Core/Union.php +++ b/seed/php-model/cross-package-type-names/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/custom-auth/src/Core/JsonDecoder.php b/seed/php-model/custom-auth/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/custom-auth/src/Core/JsonDecoder.php +++ b/seed/php-model/custom-auth/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/custom-auth/src/Core/JsonDeserializer.php b/seed/php-model/custom-auth/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/custom-auth/src/Core/JsonDeserializer.php +++ b/seed/php-model/custom-auth/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/custom-auth/src/Core/JsonSerializer.php b/seed/php-model/custom-auth/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/custom-auth/src/Core/JsonSerializer.php +++ b/seed/php-model/custom-auth/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/custom-auth/src/Core/SerializableType.php b/seed/php-model/custom-auth/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/custom-auth/src/Core/SerializableType.php +++ b/seed/php-model/custom-auth/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/custom-auth/src/Core/Union.php b/seed/php-model/custom-auth/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/custom-auth/src/Core/Union.php +++ b/seed/php-model/custom-auth/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/enum/src/Core/JsonDecoder.php b/seed/php-model/enum/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/enum/src/Core/JsonDecoder.php +++ b/seed/php-model/enum/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/enum/src/Core/JsonDeserializer.php b/seed/php-model/enum/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/enum/src/Core/JsonDeserializer.php +++ b/seed/php-model/enum/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/enum/src/Core/JsonSerializer.php b/seed/php-model/enum/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/enum/src/Core/JsonSerializer.php +++ b/seed/php-model/enum/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/enum/src/Core/SerializableType.php b/seed/php-model/enum/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/enum/src/Core/SerializableType.php +++ b/seed/php-model/enum/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/enum/src/Core/Union.php b/seed/php-model/enum/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/enum/src/Core/Union.php +++ b/seed/php-model/enum/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/enum/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/enum/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/error-property/src/Core/JsonDecoder.php b/seed/php-model/error-property/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/error-property/src/Core/JsonDecoder.php +++ b/seed/php-model/error-property/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/error-property/src/Core/JsonDeserializer.php b/seed/php-model/error-property/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/error-property/src/Core/JsonDeserializer.php +++ b/seed/php-model/error-property/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/error-property/src/Core/JsonSerializer.php b/seed/php-model/error-property/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/error-property/src/Core/JsonSerializer.php +++ b/seed/php-model/error-property/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/error-property/src/Core/SerializableType.php b/seed/php-model/error-property/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/error-property/src/Core/SerializableType.php +++ b/seed/php-model/error-property/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/error-property/src/Core/Union.php b/seed/php-model/error-property/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/error-property/src/Core/Union.php +++ b/seed/php-model/error-property/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/error-property/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/examples/src/Core/JsonDecoder.php b/seed/php-model/examples/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/examples/src/Core/JsonDecoder.php +++ b/seed/php-model/examples/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/examples/src/Core/JsonDeserializer.php b/seed/php-model/examples/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/examples/src/Core/JsonDeserializer.php +++ b/seed/php-model/examples/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/examples/src/Core/JsonSerializer.php b/seed/php-model/examples/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/examples/src/Core/JsonSerializer.php +++ b/seed/php-model/examples/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/examples/src/Core/SerializableType.php b/seed/php-model/examples/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/examples/src/Core/SerializableType.php +++ b/seed/php-model/examples/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/examples/src/Core/Union.php b/seed/php-model/examples/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/examples/src/Core/Union.php +++ b/seed/php-model/examples/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/examples/src/Identifier.php b/seed/php-model/examples/src/Identifier.php index e673d13197e..48363b1db26 100644 --- a/seed/php-model/examples/src/Identifier.php +++ b/seed/php-model/examples/src/Identifier.php @@ -8,10 +8,10 @@ class Identifier extends SerializableType { /** - * @var mixed $type + * @var value-of|value-of $type */ #[JsonProperty('type')] - public mixed $type; + public string $type; /** * @var string $value @@ -27,7 +27,7 @@ class Identifier extends SerializableType /** * @param array{ - * type: mixed, + * type: value-of|value-of, * value: string, * label: string, * } $values diff --git a/seed/php-model/examples/src/Types/Entity.php b/seed/php-model/examples/src/Types/Entity.php index c1dd816bc67..c94f68a9794 100644 --- a/seed/php-model/examples/src/Types/Entity.php +++ b/seed/php-model/examples/src/Types/Entity.php @@ -3,15 +3,17 @@ namespace Seed\Types; use Seed\Core\SerializableType; +use Seed\BasicType; +use Seed\ComplexType; use Seed\Core\JsonProperty; class Entity extends SerializableType { /** - * @var mixed $type + * @var value-of|value-of $type */ #[JsonProperty('type')] - public mixed $type; + public string $type; /** * @var string $name @@ -21,7 +23,7 @@ class Entity extends SerializableType /** * @param array{ - * type: mixed, + * type: value-of|value-of, * name: string, * } $values */ diff --git a/seed/php-model/examples/src/Types/ResponseType.php b/seed/php-model/examples/src/Types/ResponseType.php index fd26020ff99..6bdca9a0b71 100644 --- a/seed/php-model/examples/src/Types/ResponseType.php +++ b/seed/php-model/examples/src/Types/ResponseType.php @@ -3,19 +3,21 @@ namespace Seed\Types; use Seed\Core\SerializableType; +use Seed\BasicType; +use Seed\ComplexType; use Seed\Core\JsonProperty; class ResponseType extends SerializableType { /** - * @var mixed $type + * @var value-of|value-of $type */ #[JsonProperty('type')] - public mixed $type; + public string $type; /** * @param array{ - * type: mixed, + * type: value-of|value-of, * } $values */ public function __construct( diff --git a/seed/php-model/examples/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/examples/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/exhaustive/src/Core/JsonDecoder.php b/seed/php-model/exhaustive/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/exhaustive/src/Core/JsonDecoder.php +++ b/seed/php-model/exhaustive/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/exhaustive/src/Core/JsonDeserializer.php b/seed/php-model/exhaustive/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/exhaustive/src/Core/JsonDeserializer.php +++ b/seed/php-model/exhaustive/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/exhaustive/src/Core/JsonSerializer.php b/seed/php-model/exhaustive/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/exhaustive/src/Core/JsonSerializer.php +++ b/seed/php-model/exhaustive/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/exhaustive/src/Core/SerializableType.php b/seed/php-model/exhaustive/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/exhaustive/src/Core/SerializableType.php +++ b/seed/php-model/exhaustive/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/exhaustive/src/Core/Union.php b/seed/php-model/exhaustive/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/exhaustive/src/Core/Union.php +++ b/seed/php-model/exhaustive/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/extends/src/Core/JsonDecoder.php b/seed/php-model/extends/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/extends/src/Core/JsonDecoder.php +++ b/seed/php-model/extends/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/extends/src/Core/JsonDeserializer.php b/seed/php-model/extends/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/extends/src/Core/JsonDeserializer.php +++ b/seed/php-model/extends/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/extends/src/Core/JsonSerializer.php b/seed/php-model/extends/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/extends/src/Core/JsonSerializer.php +++ b/seed/php-model/extends/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/extends/src/Core/SerializableType.php b/seed/php-model/extends/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/extends/src/Core/SerializableType.php +++ b/seed/php-model/extends/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/extends/src/Core/Union.php b/seed/php-model/extends/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/extends/src/Core/Union.php +++ b/seed/php-model/extends/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/extends/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/extends/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/extra-properties/src/Core/JsonDecoder.php b/seed/php-model/extra-properties/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/extra-properties/src/Core/JsonDecoder.php +++ b/seed/php-model/extra-properties/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/extra-properties/src/Core/JsonDeserializer.php b/seed/php-model/extra-properties/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/extra-properties/src/Core/JsonDeserializer.php +++ b/seed/php-model/extra-properties/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/extra-properties/src/Core/JsonSerializer.php b/seed/php-model/extra-properties/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/extra-properties/src/Core/JsonSerializer.php +++ b/seed/php-model/extra-properties/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/extra-properties/src/Core/SerializableType.php b/seed/php-model/extra-properties/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/extra-properties/src/Core/SerializableType.php +++ b/seed/php-model/extra-properties/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/extra-properties/src/Core/Union.php b/seed/php-model/extra-properties/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/extra-properties/src/Core/Union.php +++ b/seed/php-model/extra-properties/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/file-download/src/Core/JsonDecoder.php b/seed/php-model/file-download/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/file-download/src/Core/JsonDecoder.php +++ b/seed/php-model/file-download/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/file-download/src/Core/JsonDeserializer.php b/seed/php-model/file-download/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/file-download/src/Core/JsonDeserializer.php +++ b/seed/php-model/file-download/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/file-download/src/Core/JsonSerializer.php b/seed/php-model/file-download/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/file-download/src/Core/JsonSerializer.php +++ b/seed/php-model/file-download/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/file-download/src/Core/SerializableType.php b/seed/php-model/file-download/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/file-download/src/Core/SerializableType.php +++ b/seed/php-model/file-download/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/file-download/src/Core/Union.php b/seed/php-model/file-download/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/file-download/src/Core/Union.php +++ b/seed/php-model/file-download/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/file-download/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/file-upload/src/Core/JsonDecoder.php b/seed/php-model/file-upload/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/file-upload/src/Core/JsonDecoder.php +++ b/seed/php-model/file-upload/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/file-upload/src/Core/JsonDeserializer.php b/seed/php-model/file-upload/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/file-upload/src/Core/JsonDeserializer.php +++ b/seed/php-model/file-upload/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/file-upload/src/Core/JsonSerializer.php b/seed/php-model/file-upload/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/file-upload/src/Core/JsonSerializer.php +++ b/seed/php-model/file-upload/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/file-upload/src/Core/SerializableType.php b/seed/php-model/file-upload/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/file-upload/src/Core/SerializableType.php +++ b/seed/php-model/file-upload/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/file-upload/src/Core/Union.php b/seed/php-model/file-upload/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/file-upload/src/Core/Union.php +++ b/seed/php-model/file-upload/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/folders/src/Core/JsonDecoder.php b/seed/php-model/folders/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/folders/src/Core/JsonDecoder.php +++ b/seed/php-model/folders/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/folders/src/Core/JsonDeserializer.php b/seed/php-model/folders/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/folders/src/Core/JsonDeserializer.php +++ b/seed/php-model/folders/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/folders/src/Core/JsonSerializer.php b/seed/php-model/folders/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/folders/src/Core/JsonSerializer.php +++ b/seed/php-model/folders/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/folders/src/Core/SerializableType.php b/seed/php-model/folders/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/folders/src/Core/SerializableType.php +++ b/seed/php-model/folders/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/folders/src/Core/Union.php b/seed/php-model/folders/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/folders/src/Core/Union.php +++ b/seed/php-model/folders/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/folders/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/folders/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Column.php b/seed/php-model/grpc-proto-exhaustive/src/Column.php index 0d6262dcadf..c0cc5b4386c 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Column.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Column.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class Column extends SerializableType { @@ -21,10 +22,10 @@ class Column extends SerializableType public array $values; /** - * @var mixed $metadata + * @var array|array|null $metadata */ - #[JsonProperty('metadata')] - public mixed $metadata; + #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $metadata; /** * @var ?IndexedData $indexedData @@ -36,7 +37,7 @@ class Column extends SerializableType * @param array{ * id: string, * values: array, - * metadata: mixed, + * metadata?: array|array|null, * indexedData?: ?IndexedData, * } $values */ @@ -45,7 +46,7 @@ public function __construct( ) { $this->id = $values['id']; $this->values = $values['values']; - $this->metadata = $values['metadata']; + $this->metadata = $values['metadata'] ?? null; $this->indexedData = $values['indexedData'] ?? null; } } diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDecoder.php b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDecoder.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDeserializer.php b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDeserializer.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonSerializer.php b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonSerializer.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/SerializableType.php b/seed/php-model/grpc-proto-exhaustive/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/SerializableType.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Union.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/Union.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php b/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php index a21ff3222ff..c30b57e5a54 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php +++ b/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class QueryColumn extends SerializableType { @@ -27,10 +28,10 @@ class QueryColumn extends SerializableType public ?string $namespace; /** - * @var mixed $filter + * @var array|array|null $filter */ - #[JsonProperty('filter')] - public mixed $filter; + #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $filter; /** * @var ?IndexedData $indexedData @@ -43,7 +44,7 @@ class QueryColumn extends SerializableType * values: array, * topK?: ?int, * namespace?: ?string, - * filter: mixed, + * filter?: array|array|null, * indexedData?: ?IndexedData, * } $values */ @@ -53,7 +54,7 @@ public function __construct( $this->values = $values['values']; $this->topK = $values['topK'] ?? null; $this->namespace = $values['namespace'] ?? null; - $this->filter = $values['filter']; + $this->filter = $values['filter'] ?? null; $this->indexedData = $values['indexedData'] ?? null; } } diff --git a/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php b/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php index 021ff382e18..22ce1d2861d 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php +++ b/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class ScoredColumn extends SerializableType { @@ -27,10 +28,10 @@ class ScoredColumn extends SerializableType public ?array $values; /** - * @var mixed $metadata + * @var array|array|null $metadata */ - #[JsonProperty('metadata')] - public mixed $metadata; + #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $metadata; /** * @var ?IndexedData $indexedData @@ -43,7 +44,7 @@ class ScoredColumn extends SerializableType * id: string, * score?: ?float, * values?: ?array, - * metadata: mixed, + * metadata?: array|array|null, * indexedData?: ?IndexedData, * } $values */ @@ -53,7 +54,7 @@ public function __construct( $this->id = $values['id']; $this->score = $values['score'] ?? null; $this->values = $values['values'] ?? null; - $this->metadata = $values['metadata']; + $this->metadata = $values['metadata'] ?? null; $this->indexedData = $values['indexedData'] ?? null; } } diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/grpc-proto/src/Core/JsonDecoder.php b/seed/php-model/grpc-proto/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/grpc-proto/src/Core/JsonDecoder.php +++ b/seed/php-model/grpc-proto/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/grpc-proto/src/Core/JsonDeserializer.php b/seed/php-model/grpc-proto/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/grpc-proto/src/Core/JsonDeserializer.php +++ b/seed/php-model/grpc-proto/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/grpc-proto/src/Core/JsonSerializer.php b/seed/php-model/grpc-proto/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/grpc-proto/src/Core/JsonSerializer.php +++ b/seed/php-model/grpc-proto/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/grpc-proto/src/Core/SerializableType.php b/seed/php-model/grpc-proto/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/grpc-proto/src/Core/SerializableType.php +++ b/seed/php-model/grpc-proto/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/grpc-proto/src/Core/Union.php b/seed/php-model/grpc-proto/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/grpc-proto/src/Core/Union.php +++ b/seed/php-model/grpc-proto/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/grpc-proto/src/UserModel.php b/seed/php-model/grpc-proto/src/UserModel.php index 4ba096631ea..54516ab941a 100644 --- a/seed/php-model/grpc-proto/src/UserModel.php +++ b/seed/php-model/grpc-proto/src/UserModel.php @@ -4,6 +4,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; +use Seed\Core\Union; class UserModel extends SerializableType { @@ -32,10 +33,10 @@ class UserModel extends SerializableType public ?float $weight; /** - * @var mixed $metadata + * @var array|array|null $metadata */ - #[JsonProperty('metadata')] - public mixed $metadata; + #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $metadata; /** * @param array{ @@ -43,16 +44,16 @@ class UserModel extends SerializableType * email?: ?string, * age?: ?int, * weight?: ?float, - * metadata: mixed, + * metadata?: array|array|null, * } $values */ public function __construct( - array $values, + array $values = [], ) { $this->username = $values['username'] ?? null; $this->email = $values['email'] ?? null; $this->age = $values['age'] ?? null; $this->weight = $values['weight'] ?? null; - $this->metadata = $values['metadata']; + $this->metadata = $values['metadata'] ?? null; } } diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/idempotency-headers/src/Core/JsonDecoder.php b/seed/php-model/idempotency-headers/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/idempotency-headers/src/Core/JsonDecoder.php +++ b/seed/php-model/idempotency-headers/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/idempotency-headers/src/Core/JsonDeserializer.php b/seed/php-model/idempotency-headers/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/idempotency-headers/src/Core/JsonDeserializer.php +++ b/seed/php-model/idempotency-headers/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/idempotency-headers/src/Core/JsonSerializer.php b/seed/php-model/idempotency-headers/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/idempotency-headers/src/Core/JsonSerializer.php +++ b/seed/php-model/idempotency-headers/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/idempotency-headers/src/Core/SerializableType.php b/seed/php-model/idempotency-headers/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/idempotency-headers/src/Core/SerializableType.php +++ b/seed/php-model/idempotency-headers/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/idempotency-headers/src/Core/Union.php b/seed/php-model/idempotency-headers/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/idempotency-headers/src/Core/Union.php +++ b/seed/php-model/idempotency-headers/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/imdb/src/Core/JsonDecoder.php b/seed/php-model/imdb/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/imdb/src/Core/JsonDecoder.php +++ b/seed/php-model/imdb/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/imdb/src/Core/JsonDeserializer.php b/seed/php-model/imdb/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/imdb/src/Core/JsonDeserializer.php +++ b/seed/php-model/imdb/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/imdb/src/Core/JsonSerializer.php b/seed/php-model/imdb/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/imdb/src/Core/JsonSerializer.php +++ b/seed/php-model/imdb/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/imdb/src/Core/SerializableType.php b/seed/php-model/imdb/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/imdb/src/Core/SerializableType.php +++ b/seed/php-model/imdb/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/imdb/src/Core/Union.php b/seed/php-model/imdb/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/imdb/src/Core/Union.php +++ b/seed/php-model/imdb/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/imdb/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/literal/src/Core/JsonDecoder.php b/seed/php-model/literal/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/literal/src/Core/JsonDecoder.php +++ b/seed/php-model/literal/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/literal/src/Core/JsonDeserializer.php b/seed/php-model/literal/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/literal/src/Core/JsonDeserializer.php +++ b/seed/php-model/literal/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/literal/src/Core/JsonSerializer.php b/seed/php-model/literal/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/literal/src/Core/JsonSerializer.php +++ b/seed/php-model/literal/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/literal/src/Core/SerializableType.php b/seed/php-model/literal/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/literal/src/Core/SerializableType.php +++ b/seed/php-model/literal/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/literal/src/Core/Union.php b/seed/php-model/literal/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/literal/src/Core/Union.php +++ b/seed/php-model/literal/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/literal/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/literal/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/mixed-case/src/Core/JsonDecoder.php b/seed/php-model/mixed-case/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/mixed-case/src/Core/JsonDecoder.php +++ b/seed/php-model/mixed-case/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/mixed-case/src/Core/JsonDeserializer.php b/seed/php-model/mixed-case/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/mixed-case/src/Core/JsonDeserializer.php +++ b/seed/php-model/mixed-case/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/mixed-case/src/Core/JsonSerializer.php b/seed/php-model/mixed-case/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/mixed-case/src/Core/JsonSerializer.php +++ b/seed/php-model/mixed-case/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/mixed-case/src/Core/SerializableType.php b/seed/php-model/mixed-case/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/mixed-case/src/Core/SerializableType.php +++ b/seed/php-model/mixed-case/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/mixed-case/src/Core/Union.php b/seed/php-model/mixed-case/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/mixed-case/src/Core/Union.php +++ b/seed/php-model/mixed-case/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/mixed-file-directory/src/Core/JsonDecoder.php b/seed/php-model/mixed-file-directory/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/mixed-file-directory/src/Core/JsonDecoder.php +++ b/seed/php-model/mixed-file-directory/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/mixed-file-directory/src/Core/JsonDeserializer.php b/seed/php-model/mixed-file-directory/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/mixed-file-directory/src/Core/JsonDeserializer.php +++ b/seed/php-model/mixed-file-directory/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/mixed-file-directory/src/Core/JsonSerializer.php b/seed/php-model/mixed-file-directory/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/mixed-file-directory/src/Core/JsonSerializer.php +++ b/seed/php-model/mixed-file-directory/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/mixed-file-directory/src/Core/SerializableType.php b/seed/php-model/mixed-file-directory/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/mixed-file-directory/src/Core/SerializableType.php +++ b/seed/php-model/mixed-file-directory/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/mixed-file-directory/src/Core/Union.php b/seed/php-model/mixed-file-directory/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/mixed-file-directory/src/Core/Union.php +++ b/seed/php-model/mixed-file-directory/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/multi-line-docs/src/Core/JsonDecoder.php b/seed/php-model/multi-line-docs/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/multi-line-docs/src/Core/JsonDecoder.php +++ b/seed/php-model/multi-line-docs/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/multi-line-docs/src/Core/JsonDeserializer.php b/seed/php-model/multi-line-docs/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/multi-line-docs/src/Core/JsonDeserializer.php +++ b/seed/php-model/multi-line-docs/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/multi-line-docs/src/Core/JsonSerializer.php b/seed/php-model/multi-line-docs/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/multi-line-docs/src/Core/JsonSerializer.php +++ b/seed/php-model/multi-line-docs/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/multi-line-docs/src/Core/SerializableType.php b/seed/php-model/multi-line-docs/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/multi-line-docs/src/Core/SerializableType.php +++ b/seed/php-model/multi-line-docs/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/multi-line-docs/src/Core/Union.php b/seed/php-model/multi-line-docs/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/multi-line-docs/src/Core/Union.php +++ b/seed/php-model/multi-line-docs/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/JsonDecoder.php b/seed/php-model/multi-url-environment-no-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/multi-url-environment-no-default/src/Core/JsonDecoder.php +++ b/seed/php-model/multi-url-environment-no-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/JsonDeserializer.php b/seed/php-model/multi-url-environment-no-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/multi-url-environment-no-default/src/Core/JsonDeserializer.php +++ b/seed/php-model/multi-url-environment-no-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/JsonSerializer.php b/seed/php-model/multi-url-environment-no-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/multi-url-environment-no-default/src/Core/JsonSerializer.php +++ b/seed/php-model/multi-url-environment-no-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/SerializableType.php b/seed/php-model/multi-url-environment-no-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/multi-url-environment-no-default/src/Core/SerializableType.php +++ b/seed/php-model/multi-url-environment-no-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Union.php b/seed/php-model/multi-url-environment-no-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/multi-url-environment-no-default/src/Core/Union.php +++ b/seed/php-model/multi-url-environment-no-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/multi-url-environment/src/Core/JsonDecoder.php b/seed/php-model/multi-url-environment/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/multi-url-environment/src/Core/JsonDecoder.php +++ b/seed/php-model/multi-url-environment/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/multi-url-environment/src/Core/JsonDeserializer.php b/seed/php-model/multi-url-environment/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/multi-url-environment/src/Core/JsonDeserializer.php +++ b/seed/php-model/multi-url-environment/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/multi-url-environment/src/Core/JsonSerializer.php b/seed/php-model/multi-url-environment/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/multi-url-environment/src/Core/JsonSerializer.php +++ b/seed/php-model/multi-url-environment/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/multi-url-environment/src/Core/SerializableType.php b/seed/php-model/multi-url-environment/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/multi-url-environment/src/Core/SerializableType.php +++ b/seed/php-model/multi-url-environment/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/multi-url-environment/src/Core/Union.php b/seed/php-model/multi-url-environment/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/multi-url-environment/src/Core/Union.php +++ b/seed/php-model/multi-url-environment/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/no-environment/src/Core/JsonDecoder.php b/seed/php-model/no-environment/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/no-environment/src/Core/JsonDecoder.php +++ b/seed/php-model/no-environment/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/no-environment/src/Core/JsonDeserializer.php b/seed/php-model/no-environment/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/no-environment/src/Core/JsonDeserializer.php +++ b/seed/php-model/no-environment/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/no-environment/src/Core/JsonSerializer.php b/seed/php-model/no-environment/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/no-environment/src/Core/JsonSerializer.php +++ b/seed/php-model/no-environment/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/no-environment/src/Core/SerializableType.php b/seed/php-model/no-environment/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/no-environment/src/Core/SerializableType.php +++ b/seed/php-model/no-environment/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/no-environment/src/Core/Union.php b/seed/php-model/no-environment/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/no-environment/src/Core/Union.php +++ b/seed/php-model/no-environment/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/JsonDecoder.php b/seed/php-model/oauth-client-credentials-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/oauth-client-credentials-default/src/Core/JsonDecoder.php +++ b/seed/php-model/oauth-client-credentials-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/oauth-client-credentials-default/src/Core/JsonDeserializer.php +++ b/seed/php-model/oauth-client-credentials-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/JsonSerializer.php b/seed/php-model/oauth-client-credentials-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/oauth-client-credentials-default/src/Core/JsonSerializer.php +++ b/seed/php-model/oauth-client-credentials-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/SerializableType.php b/seed/php-model/oauth-client-credentials-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/oauth-client-credentials-default/src/Core/SerializableType.php +++ b/seed/php-model/oauth-client-credentials-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Union.php b/seed/php-model/oauth-client-credentials-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/oauth-client-credentials-default/src/Core/Union.php +++ b/seed/php-model/oauth-client-credentials-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/SerializableType.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/SerializableType.php +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Union.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Union.php +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/SerializableType.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/SerializableType.php +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Union.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Union.php +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/src/Core/JsonDecoder.php b/seed/php-model/oauth-client-credentials/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/oauth-client-credentials/src/Core/JsonDecoder.php +++ b/seed/php-model/oauth-client-credentials/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/oauth-client-credentials/src/Core/JsonDeserializer.php b/seed/php-model/oauth-client-credentials/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/oauth-client-credentials/src/Core/JsonDeserializer.php +++ b/seed/php-model/oauth-client-credentials/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/oauth-client-credentials/src/Core/JsonSerializer.php b/seed/php-model/oauth-client-credentials/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/oauth-client-credentials/src/Core/JsonSerializer.php +++ b/seed/php-model/oauth-client-credentials/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/oauth-client-credentials/src/Core/SerializableType.php b/seed/php-model/oauth-client-credentials/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/oauth-client-credentials/src/Core/SerializableType.php +++ b/seed/php-model/oauth-client-credentials/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/oauth-client-credentials/src/Core/Union.php b/seed/php-model/oauth-client-credentials/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/oauth-client-credentials/src/Core/Union.php +++ b/seed/php-model/oauth-client-credentials/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/object/src/Core/JsonDecoder.php b/seed/php-model/object/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/object/src/Core/JsonDecoder.php +++ b/seed/php-model/object/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/object/src/Core/JsonDeserializer.php b/seed/php-model/object/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/object/src/Core/JsonDeserializer.php +++ b/seed/php-model/object/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/object/src/Core/JsonSerializer.php b/seed/php-model/object/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/object/src/Core/JsonSerializer.php +++ b/seed/php-model/object/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/object/src/Core/SerializableType.php b/seed/php-model/object/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/object/src/Core/SerializableType.php +++ b/seed/php-model/object/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/object/src/Core/Union.php b/seed/php-model/object/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/object/src/Core/Union.php +++ b/seed/php-model/object/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/object/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/object/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/objects-with-imports/src/Core/JsonDecoder.php b/seed/php-model/objects-with-imports/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/objects-with-imports/src/Core/JsonDecoder.php +++ b/seed/php-model/objects-with-imports/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/objects-with-imports/src/Core/JsonDeserializer.php b/seed/php-model/objects-with-imports/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/objects-with-imports/src/Core/JsonDeserializer.php +++ b/seed/php-model/objects-with-imports/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/objects-with-imports/src/Core/JsonSerializer.php b/seed/php-model/objects-with-imports/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/objects-with-imports/src/Core/JsonSerializer.php +++ b/seed/php-model/objects-with-imports/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/objects-with-imports/src/Core/SerializableType.php b/seed/php-model/objects-with-imports/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/objects-with-imports/src/Core/SerializableType.php +++ b/seed/php-model/objects-with-imports/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/objects-with-imports/src/Core/Union.php b/seed/php-model/objects-with-imports/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/objects-with-imports/src/Core/Union.php +++ b/seed/php-model/objects-with-imports/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/optional/src/Core/JsonDecoder.php b/seed/php-model/optional/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/optional/src/Core/JsonDecoder.php +++ b/seed/php-model/optional/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/optional/src/Core/JsonDeserializer.php b/seed/php-model/optional/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/optional/src/Core/JsonDeserializer.php +++ b/seed/php-model/optional/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/optional/src/Core/JsonSerializer.php b/seed/php-model/optional/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/optional/src/Core/JsonSerializer.php +++ b/seed/php-model/optional/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/optional/src/Core/SerializableType.php b/seed/php-model/optional/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/optional/src/Core/SerializableType.php +++ b/seed/php-model/optional/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/optional/src/Core/Union.php b/seed/php-model/optional/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/optional/src/Core/Union.php +++ b/seed/php-model/optional/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/optional/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/optional/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/package-yml/src/Core/JsonDecoder.php b/seed/php-model/package-yml/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/package-yml/src/Core/JsonDecoder.php +++ b/seed/php-model/package-yml/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/package-yml/src/Core/JsonDeserializer.php b/seed/php-model/package-yml/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/package-yml/src/Core/JsonDeserializer.php +++ b/seed/php-model/package-yml/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/package-yml/src/Core/JsonSerializer.php b/seed/php-model/package-yml/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/package-yml/src/Core/JsonSerializer.php +++ b/seed/php-model/package-yml/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/package-yml/src/Core/SerializableType.php b/seed/php-model/package-yml/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/package-yml/src/Core/SerializableType.php +++ b/seed/php-model/package-yml/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/package-yml/src/Core/Union.php b/seed/php-model/package-yml/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/package-yml/src/Core/Union.php +++ b/seed/php-model/package-yml/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/pagination/src/Core/JsonDecoder.php b/seed/php-model/pagination/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/pagination/src/Core/JsonDecoder.php +++ b/seed/php-model/pagination/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/pagination/src/Core/JsonDeserializer.php b/seed/php-model/pagination/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/pagination/src/Core/JsonDeserializer.php +++ b/seed/php-model/pagination/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/pagination/src/Core/JsonSerializer.php b/seed/php-model/pagination/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/pagination/src/Core/JsonSerializer.php +++ b/seed/php-model/pagination/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/pagination/src/Core/SerializableType.php b/seed/php-model/pagination/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/pagination/src/Core/SerializableType.php +++ b/seed/php-model/pagination/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/pagination/src/Core/Union.php b/seed/php-model/pagination/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/pagination/src/Core/Union.php +++ b/seed/php-model/pagination/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/pagination/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/plain-text/src/Core/JsonDecoder.php b/seed/php-model/plain-text/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/plain-text/src/Core/JsonDecoder.php +++ b/seed/php-model/plain-text/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/plain-text/src/Core/JsonDeserializer.php b/seed/php-model/plain-text/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/plain-text/src/Core/JsonDeserializer.php +++ b/seed/php-model/plain-text/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/plain-text/src/Core/JsonSerializer.php b/seed/php-model/plain-text/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/plain-text/src/Core/JsonSerializer.php +++ b/seed/php-model/plain-text/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/plain-text/src/Core/SerializableType.php b/seed/php-model/plain-text/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/plain-text/src/Core/SerializableType.php +++ b/seed/php-model/plain-text/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/plain-text/src/Core/Union.php b/seed/php-model/plain-text/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/plain-text/src/Core/Union.php +++ b/seed/php-model/plain-text/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/query-parameters/src/Core/JsonDecoder.php b/seed/php-model/query-parameters/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/query-parameters/src/Core/JsonDecoder.php +++ b/seed/php-model/query-parameters/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/query-parameters/src/Core/JsonDeserializer.php b/seed/php-model/query-parameters/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/query-parameters/src/Core/JsonDeserializer.php +++ b/seed/php-model/query-parameters/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/query-parameters/src/Core/JsonSerializer.php b/seed/php-model/query-parameters/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/query-parameters/src/Core/JsonSerializer.php +++ b/seed/php-model/query-parameters/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/query-parameters/src/Core/SerializableType.php b/seed/php-model/query-parameters/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/query-parameters/src/Core/SerializableType.php +++ b/seed/php-model/query-parameters/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/query-parameters/src/Core/Union.php b/seed/php-model/query-parameters/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/query-parameters/src/Core/Union.php +++ b/seed/php-model/query-parameters/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/reserved-keywords/src/Core/JsonDecoder.php b/seed/php-model/reserved-keywords/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/reserved-keywords/src/Core/JsonDecoder.php +++ b/seed/php-model/reserved-keywords/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/reserved-keywords/src/Core/JsonDeserializer.php b/seed/php-model/reserved-keywords/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/reserved-keywords/src/Core/JsonDeserializer.php +++ b/seed/php-model/reserved-keywords/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/reserved-keywords/src/Core/JsonSerializer.php b/seed/php-model/reserved-keywords/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/reserved-keywords/src/Core/JsonSerializer.php +++ b/seed/php-model/reserved-keywords/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/reserved-keywords/src/Core/SerializableType.php b/seed/php-model/reserved-keywords/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/reserved-keywords/src/Core/SerializableType.php +++ b/seed/php-model/reserved-keywords/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/reserved-keywords/src/Core/Union.php b/seed/php-model/reserved-keywords/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/reserved-keywords/src/Core/Union.php +++ b/seed/php-model/reserved-keywords/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/response-property/src/Core/JsonDecoder.php b/seed/php-model/response-property/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/response-property/src/Core/JsonDecoder.php +++ b/seed/php-model/response-property/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/response-property/src/Core/JsonDeserializer.php b/seed/php-model/response-property/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/response-property/src/Core/JsonDeserializer.php +++ b/seed/php-model/response-property/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/response-property/src/Core/JsonSerializer.php b/seed/php-model/response-property/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/response-property/src/Core/JsonSerializer.php +++ b/seed/php-model/response-property/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/response-property/src/Core/SerializableType.php b/seed/php-model/response-property/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/response-property/src/Core/SerializableType.php +++ b/seed/php-model/response-property/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/response-property/src/Core/Union.php b/seed/php-model/response-property/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/response-property/src/Core/Union.php +++ b/seed/php-model/response-property/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/response-property/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/.github/workflows/ci.yml b/seed/php-model/server-sent-event-examples/.github/workflows/ci.yml new file mode 100644 index 00000000000..258bf33a19f --- /dev/null +++ b/seed/php-model/server-sent-event-examples/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test \ No newline at end of file diff --git a/seed/php-model/server-sent-event-examples/.gitignore b/seed/php-model/server-sent-event-examples/.gitignore new file mode 100644 index 00000000000..f38efc46ade --- /dev/null +++ b/seed/php-model/server-sent-event-examples/.gitignore @@ -0,0 +1,4 @@ +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-model/server-sent-event-examples/.mock/definition/api.yml b/seed/php-model/server-sent-event-examples/.mock/definition/api.yml new file mode 100644 index 00000000000..80e84c41785 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/.mock/definition/api.yml @@ -0,0 +1 @@ +name: server-sent-events diff --git a/seed/php-model/server-sent-event-examples/.mock/definition/completions.yml b/seed/php-model/server-sent-event-examples/.mock/definition/completions.yml new file mode 100644 index 00000000000..09a88253331 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/.mock/definition/completions.yml @@ -0,0 +1,36 @@ +types: + StreamedCompletion: + properties: + delta: string + tokens: optional + +service: + auth: false + base-path: "" + endpoints: + stream: + method: POST + path: /stream + request: + name: StreamCompletionRequest + body: + properties: + query: string + response-stream: + type: StreamedCompletion + format: sse + terminator: "[[DONE]]" + examples: + - name: "Stream completions" + request: + query: "foo" + response: + stream: + - event: discriminant-1 + data: + delta: "foo" + tokens: 1 + - event: discriminant-2 + data: + delta: "bar" + tokens: 2 diff --git a/seed/php-model/server-sent-event-examples/.mock/fern.config.json b/seed/php-model/server-sent-event-examples/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/php-model/server-sent-event-examples/.mock/generators.yml b/seed/php-model/server-sent-event-examples/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/php-model/server-sent-event-examples/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/php-model/server-sent-event-examples/composer.json b/seed/php-model/server-sent-event-examples/composer.json new file mode 100644 index 00000000000..7f5821806d4 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/composer.json @@ -0,0 +1,40 @@ + +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.9" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "\\Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src" + } +} diff --git a/seed/php-model/server-sent-event-examples/phpstan.neon b/seed/php-model/server-sent-event-examples/phpstan.neon new file mode 100644 index 00000000000..29a11a92a19 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-model/server-sent-event-examples/phpunit.xml b/seed/php-model/server-sent-event-examples/phpunit.xml new file mode 100644 index 00000000000..54630a51163 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-model/server-sent-event-examples/snippet-templates.json b/seed/php-model/server-sent-event-examples/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/server-sent-event-examples/snippet.json b/seed/php-model/server-sent-event-examples/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/server-sent-event-examples/src/Completions/StreamedCompletion.php b/seed/php-model/server-sent-event-examples/src/Completions/StreamedCompletion.php new file mode 100644 index 00000000000..59208a9fc57 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Completions/StreamedCompletion.php @@ -0,0 +1,34 @@ +delta = $values['delta']; + $this->tokens = $values['tokens'] ?? null; + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/ArrayType.php b/seed/php-model/server-sent-event-examples/src/Core/ArrayType.php new file mode 100644 index 00000000000..b2ed8bf12b2 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Constant.php b/seed/php-model/server-sent-event-examples/src/Core/Constant.php new file mode 100644 index 00000000000..abbac7f6649 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Constant.php @@ -0,0 +1,12 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/JsonDeserializer.php b/seed/php-model/server-sent-event-examples/src/Core/JsonDeserializer.php new file mode 100644 index 00000000000..b1de7d141ac --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/JsonDeserializer.php @@ -0,0 +1,202 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/JsonEncoder.php b/seed/php-model/server-sent-event-examples/src/Core/JsonEncoder.php new file mode 100644 index 00000000000..ba5191a8068 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/SerializableType.php b/seed/php-model/server-sent-event-examples/src/Core/SerializableType.php new file mode 100644 index 00000000000..9121bdca01c --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/SerializableType.php @@ -0,0 +1,179 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Union.php b/seed/php-model/server-sent-event-examples/src/Core/Union.php new file mode 100644 index 00000000000..1e9fe801ee7 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Union.php @@ -0,0 +1,62 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Utils.php b/seed/php-model/server-sent-event-examples/src/Core/Utils.php new file mode 100644 index 00000000000..74416068d02 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d93afc9e44 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php new file mode 100644 index 00000000000..b44f3d093e6 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/EnumTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/EnumTest.php new file mode 100644 index 00000000000..ef5b8484dfd --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php new file mode 100644 index 00000000000..67bfd235b2f --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..3bf18aec25b --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..4667ecafcb9 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php new file mode 100644 index 00000000000..134296f56e3 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php new file mode 100644 index 00000000000..bf6345e5c6f --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php new file mode 100644 index 00000000000..899e949836c --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php new file mode 100644 index 00000000000..8e7ca1b825c --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php new file mode 100644 index 00000000000..8d0998f4b7e --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/server-sent-events/.github/workflows/ci.yml b/seed/php-model/server-sent-events/.github/workflows/ci.yml new file mode 100644 index 00000000000..258bf33a19f --- /dev/null +++ b/seed/php-model/server-sent-events/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test \ No newline at end of file diff --git a/seed/php-model/server-sent-events/.gitignore b/seed/php-model/server-sent-events/.gitignore new file mode 100644 index 00000000000..f38efc46ade --- /dev/null +++ b/seed/php-model/server-sent-events/.gitignore @@ -0,0 +1,4 @@ +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-model/server-sent-events/.mock/definition/api.yml b/seed/php-model/server-sent-events/.mock/definition/api.yml new file mode 100644 index 00000000000..80e84c41785 --- /dev/null +++ b/seed/php-model/server-sent-events/.mock/definition/api.yml @@ -0,0 +1 @@ +name: server-sent-events diff --git a/seed/php-model/server-sent-events/.mock/definition/completions.yml b/seed/php-model/server-sent-events/.mock/definition/completions.yml new file mode 100644 index 00000000000..d1748fad19e --- /dev/null +++ b/seed/php-model/server-sent-events/.mock/definition/completions.yml @@ -0,0 +1,22 @@ +types: + StreamedCompletion: + properties: + delta: string + tokens: optional + +service: + auth: false + base-path: "" + endpoints: + stream: + method: POST + path: /stream + request: + name: StreamCompletionRequest + body: + properties: + query: string + response-stream: + type: StreamedCompletion + format: sse + terminator: "[[DONE]]" diff --git a/seed/php-model/server-sent-events/.mock/fern.config.json b/seed/php-model/server-sent-events/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/php-model/server-sent-events/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/php-model/server-sent-events/.mock/generators.yml b/seed/php-model/server-sent-events/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/php-model/server-sent-events/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/php-model/server-sent-events/composer.json b/seed/php-model/server-sent-events/composer.json new file mode 100644 index 00000000000..7f5821806d4 --- /dev/null +++ b/seed/php-model/server-sent-events/composer.json @@ -0,0 +1,40 @@ + +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.9" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "\\Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src" + } +} diff --git a/seed/php-model/server-sent-events/phpstan.neon b/seed/php-model/server-sent-events/phpstan.neon new file mode 100644 index 00000000000..29a11a92a19 --- /dev/null +++ b/seed/php-model/server-sent-events/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-model/server-sent-events/phpunit.xml b/seed/php-model/server-sent-events/phpunit.xml new file mode 100644 index 00000000000..54630a51163 --- /dev/null +++ b/seed/php-model/server-sent-events/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-model/server-sent-events/snippet-templates.json b/seed/php-model/server-sent-events/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/server-sent-events/snippet.json b/seed/php-model/server-sent-events/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/server-sent-events/src/Completions/StreamedCompletion.php b/seed/php-model/server-sent-events/src/Completions/StreamedCompletion.php new file mode 100644 index 00000000000..59208a9fc57 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Completions/StreamedCompletion.php @@ -0,0 +1,34 @@ +delta = $values['delta']; + $this->tokens = $values['tokens'] ?? null; + } +} diff --git a/seed/php-model/server-sent-events/src/Core/ArrayType.php b/seed/php-model/server-sent-events/src/Core/ArrayType.php new file mode 100644 index 00000000000..b2ed8bf12b2 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Constant.php b/seed/php-model/server-sent-events/src/Core/Constant.php new file mode 100644 index 00000000000..abbac7f6649 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Constant.php @@ -0,0 +1,12 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/server-sent-events/src/Core/JsonDeserializer.php b/seed/php-model/server-sent-events/src/Core/JsonDeserializer.php new file mode 100644 index 00000000000..b1de7d141ac --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/JsonDeserializer.php @@ -0,0 +1,202 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/server-sent-events/src/Core/JsonEncoder.php b/seed/php-model/server-sent-events/src/Core/JsonEncoder.php new file mode 100644 index 00000000000..ba5191a8068 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/server-sent-events/src/Core/SerializableType.php b/seed/php-model/server-sent-events/src/Core/SerializableType.php new file mode 100644 index 00000000000..9121bdca01c --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/SerializableType.php @@ -0,0 +1,179 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Union.php b/seed/php-model/server-sent-events/src/Core/Union.php new file mode 100644 index 00000000000..1e9fe801ee7 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Union.php @@ -0,0 +1,62 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Utils.php b/seed/php-model/server-sent-events/src/Core/Utils.php new file mode 100644 index 00000000000..74416068d02 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d93afc9e44 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/EmptyArraysTest.php new file mode 100644 index 00000000000..b44f3d093e6 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/EnumTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/EnumTest.php new file mode 100644 index 00000000000..ef5b8484dfd --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/InvalidTypesTest.php new file mode 100644 index 00000000000..67bfd235b2f --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..3bf18aec25b --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..4667ecafcb9 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php new file mode 100644 index 00000000000..134296f56e3 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php new file mode 100644 index 00000000000..bf6345e5c6f --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/ScalarTypesTest.php new file mode 100644 index 00000000000..899e949836c --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/TestTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/TestTypeTest.php new file mode 100644 index 00000000000..8e7ca1b825c --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php new file mode 100644 index 00000000000..8d0998f4b7e --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/simple-fhir/src/BaseResource.php b/seed/php-model/simple-fhir/src/BaseResource.php index fe335daac3b..ea9ceafd1db 100644 --- a/seed/php-model/simple-fhir/src/BaseResource.php +++ b/seed/php-model/simple-fhir/src/BaseResource.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class BaseResource extends SerializableType { @@ -15,9 +16,9 @@ class BaseResource extends SerializableType public string $id; /** - * @var array $relatedResources + * @var array $relatedResources */ - #[JsonProperty('related_resources'), ArrayType(['mixed'])] + #[JsonProperty('related_resources'), ArrayType([new Union(Account::class, Patient::class, Practitioner::class, Script::class)])] public array $relatedResources; /** @@ -29,7 +30,7 @@ class BaseResource extends SerializableType /** * @param array{ * id: string, - * relatedResources: array, + * relatedResources: array, * memo: Memo, * } $values */ diff --git a/seed/php-model/simple-fhir/src/Core/JsonDecoder.php b/seed/php-model/simple-fhir/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/simple-fhir/src/Core/JsonDecoder.php +++ b/seed/php-model/simple-fhir/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/simple-fhir/src/Core/JsonDeserializer.php b/seed/php-model/simple-fhir/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/simple-fhir/src/Core/JsonDeserializer.php +++ b/seed/php-model/simple-fhir/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/simple-fhir/src/Core/JsonSerializer.php b/seed/php-model/simple-fhir/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/simple-fhir/src/Core/JsonSerializer.php +++ b/seed/php-model/simple-fhir/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/simple-fhir/src/Core/SerializableType.php b/seed/php-model/simple-fhir/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/simple-fhir/src/Core/SerializableType.php +++ b/seed/php-model/simple-fhir/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/simple-fhir/src/Core/Union.php b/seed/php-model/simple-fhir/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/simple-fhir/src/Core/Union.php +++ b/seed/php-model/simple-fhir/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/single-url-environment-default/src/Core/JsonDecoder.php b/seed/php-model/single-url-environment-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/single-url-environment-default/src/Core/JsonDecoder.php +++ b/seed/php-model/single-url-environment-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/single-url-environment-default/src/Core/JsonDeserializer.php b/seed/php-model/single-url-environment-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/single-url-environment-default/src/Core/JsonDeserializer.php +++ b/seed/php-model/single-url-environment-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/single-url-environment-default/src/Core/JsonSerializer.php b/seed/php-model/single-url-environment-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/single-url-environment-default/src/Core/JsonSerializer.php +++ b/seed/php-model/single-url-environment-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/single-url-environment-default/src/Core/SerializableType.php b/seed/php-model/single-url-environment-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/single-url-environment-default/src/Core/SerializableType.php +++ b/seed/php-model/single-url-environment-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/single-url-environment-default/src/Core/Union.php b/seed/php-model/single-url-environment-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/single-url-environment-default/src/Core/Union.php +++ b/seed/php-model/single-url-environment-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/JsonDecoder.php b/seed/php-model/single-url-environment-no-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/single-url-environment-no-default/src/Core/JsonDecoder.php +++ b/seed/php-model/single-url-environment-no-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/single-url-environment-no-default/src/Core/JsonDeserializer.php b/seed/php-model/single-url-environment-no-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/single-url-environment-no-default/src/Core/JsonDeserializer.php +++ b/seed/php-model/single-url-environment-no-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/single-url-environment-no-default/src/Core/JsonSerializer.php b/seed/php-model/single-url-environment-no-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/single-url-environment-no-default/src/Core/JsonSerializer.php +++ b/seed/php-model/single-url-environment-no-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/single-url-environment-no-default/src/Core/SerializableType.php b/seed/php-model/single-url-environment-no-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/single-url-environment-no-default/src/Core/SerializableType.php +++ b/seed/php-model/single-url-environment-no-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Union.php b/seed/php-model/single-url-environment-no-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/single-url-environment-no-default/src/Core/Union.php +++ b/seed/php-model/single-url-environment-no-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/streaming-parameter/src/Core/JsonDecoder.php b/seed/php-model/streaming-parameter/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/streaming-parameter/src/Core/JsonDecoder.php +++ b/seed/php-model/streaming-parameter/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/streaming-parameter/src/Core/JsonDeserializer.php b/seed/php-model/streaming-parameter/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/streaming-parameter/src/Core/JsonDeserializer.php +++ b/seed/php-model/streaming-parameter/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/streaming-parameter/src/Core/JsonSerializer.php b/seed/php-model/streaming-parameter/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/streaming-parameter/src/Core/JsonSerializer.php +++ b/seed/php-model/streaming-parameter/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/streaming-parameter/src/Core/SerializableType.php b/seed/php-model/streaming-parameter/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/streaming-parameter/src/Core/SerializableType.php +++ b/seed/php-model/streaming-parameter/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/streaming-parameter/src/Core/Union.php b/seed/php-model/streaming-parameter/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/streaming-parameter/src/Core/Union.php +++ b/seed/php-model/streaming-parameter/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/streaming/src/Core/JsonDecoder.php b/seed/php-model/streaming/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/streaming/src/Core/JsonDecoder.php +++ b/seed/php-model/streaming/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/streaming/src/Core/JsonDeserializer.php b/seed/php-model/streaming/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/streaming/src/Core/JsonDeserializer.php +++ b/seed/php-model/streaming/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/streaming/src/Core/JsonSerializer.php b/seed/php-model/streaming/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/streaming/src/Core/JsonSerializer.php +++ b/seed/php-model/streaming/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/streaming/src/Core/SerializableType.php b/seed/php-model/streaming/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/streaming/src/Core/SerializableType.php +++ b/seed/php-model/streaming/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/streaming/src/Core/Union.php b/seed/php-model/streaming/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/streaming/src/Core/Union.php +++ b/seed/php-model/streaming/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/streaming/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/trace/src/Core/JsonDecoder.php b/seed/php-model/trace/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/trace/src/Core/JsonDecoder.php +++ b/seed/php-model/trace/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/trace/src/Core/JsonDeserializer.php b/seed/php-model/trace/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/trace/src/Core/JsonDeserializer.php +++ b/seed/php-model/trace/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/trace/src/Core/JsonSerializer.php b/seed/php-model/trace/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/trace/src/Core/JsonSerializer.php +++ b/seed/php-model/trace/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/trace/src/Core/SerializableType.php b/seed/php-model/trace/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/trace/src/Core/SerializableType.php +++ b/seed/php-model/trace/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/trace/src/Core/Union.php b/seed/php-model/trace/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/trace/src/Core/Union.php +++ b/seed/php-model/trace/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/trace/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/trace/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/src/Core/JsonDecoder.php b/seed/php-model/undiscriminated-unions/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/undiscriminated-unions/src/Core/JsonDecoder.php +++ b/seed/php-model/undiscriminated-unions/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/undiscriminated-unions/src/Core/JsonDeserializer.php b/seed/php-model/undiscriminated-unions/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/undiscriminated-unions/src/Core/JsonDeserializer.php +++ b/seed/php-model/undiscriminated-unions/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/undiscriminated-unions/src/Core/JsonSerializer.php b/seed/php-model/undiscriminated-unions/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/undiscriminated-unions/src/Core/JsonSerializer.php +++ b/seed/php-model/undiscriminated-unions/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/undiscriminated-unions/src/Core/SerializableType.php b/seed/php-model/undiscriminated-unions/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/undiscriminated-unions/src/Core/SerializableType.php +++ b/seed/php-model/undiscriminated-unions/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/undiscriminated-unions/src/Core/Union.php b/seed/php-model/undiscriminated-unions/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/undiscriminated-unions/src/Core/Union.php +++ b/seed/php-model/undiscriminated-unions/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/unions/src/Core/JsonDecoder.php b/seed/php-model/unions/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/unions/src/Core/JsonDecoder.php +++ b/seed/php-model/unions/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/unions/src/Core/JsonDeserializer.php b/seed/php-model/unions/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/unions/src/Core/JsonDeserializer.php +++ b/seed/php-model/unions/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/unions/src/Core/JsonSerializer.php b/seed/php-model/unions/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/unions/src/Core/JsonSerializer.php +++ b/seed/php-model/unions/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/unions/src/Core/SerializableType.php b/seed/php-model/unions/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/unions/src/Core/SerializableType.php +++ b/seed/php-model/unions/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/unions/src/Core/Union.php b/seed/php-model/unions/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/unions/src/Core/Union.php +++ b/seed/php-model/unions/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/unions/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/unions/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/unknown/src/Core/JsonDecoder.php b/seed/php-model/unknown/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/unknown/src/Core/JsonDecoder.php +++ b/seed/php-model/unknown/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/unknown/src/Core/JsonDeserializer.php b/seed/php-model/unknown/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/unknown/src/Core/JsonDeserializer.php +++ b/seed/php-model/unknown/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/unknown/src/Core/JsonSerializer.php b/seed/php-model/unknown/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/unknown/src/Core/JsonSerializer.php +++ b/seed/php-model/unknown/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/unknown/src/Core/SerializableType.php b/seed/php-model/unknown/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/unknown/src/Core/SerializableType.php +++ b/seed/php-model/unknown/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/unknown/src/Core/Union.php b/seed/php-model/unknown/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/unknown/src/Core/Union.php +++ b/seed/php-model/unknown/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/unknown/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/validation/src/Core/JsonDecoder.php b/seed/php-model/validation/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/validation/src/Core/JsonDecoder.php +++ b/seed/php-model/validation/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/validation/src/Core/JsonDeserializer.php b/seed/php-model/validation/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/validation/src/Core/JsonDeserializer.php +++ b/seed/php-model/validation/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/validation/src/Core/JsonSerializer.php b/seed/php-model/validation/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/validation/src/Core/JsonSerializer.php +++ b/seed/php-model/validation/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/validation/src/Core/SerializableType.php b/seed/php-model/validation/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/validation/src/Core/SerializableType.php +++ b/seed/php-model/validation/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/validation/src/Core/Union.php b/seed/php-model/validation/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/validation/src/Core/Union.php +++ b/seed/php-model/validation/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/validation/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/validation/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/variables/src/Core/JsonDecoder.php b/seed/php-model/variables/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/variables/src/Core/JsonDecoder.php +++ b/seed/php-model/variables/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/variables/src/Core/JsonDeserializer.php b/seed/php-model/variables/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/variables/src/Core/JsonDeserializer.php +++ b/seed/php-model/variables/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/variables/src/Core/JsonSerializer.php b/seed/php-model/variables/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/variables/src/Core/JsonSerializer.php +++ b/seed/php-model/variables/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/variables/src/Core/SerializableType.php b/seed/php-model/variables/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/variables/src/Core/SerializableType.php +++ b/seed/php-model/variables/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/variables/src/Core/Union.php b/seed/php-model/variables/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/variables/src/Core/Union.php +++ b/seed/php-model/variables/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/variables/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/version-no-default/src/Core/JsonDecoder.php b/seed/php-model/version-no-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/version-no-default/src/Core/JsonDecoder.php +++ b/seed/php-model/version-no-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/version-no-default/src/Core/JsonDeserializer.php b/seed/php-model/version-no-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/version-no-default/src/Core/JsonDeserializer.php +++ b/seed/php-model/version-no-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/version-no-default/src/Core/JsonSerializer.php b/seed/php-model/version-no-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/version-no-default/src/Core/JsonSerializer.php +++ b/seed/php-model/version-no-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/version-no-default/src/Core/SerializableType.php b/seed/php-model/version-no-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/version-no-default/src/Core/SerializableType.php +++ b/seed/php-model/version-no-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/version-no-default/src/Core/Union.php b/seed/php-model/version-no-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/version-no-default/src/Core/Union.php +++ b/seed/php-model/version-no-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/version/src/Core/JsonDecoder.php b/seed/php-model/version/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/version/src/Core/JsonDecoder.php +++ b/seed/php-model/version/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/version/src/Core/JsonDeserializer.php b/seed/php-model/version/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/version/src/Core/JsonDeserializer.php +++ b/seed/php-model/version/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/version/src/Core/JsonSerializer.php b/seed/php-model/version/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/version/src/Core/JsonSerializer.php +++ b/seed/php-model/version/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/version/src/Core/SerializableType.php b/seed/php-model/version/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/version/src/Core/SerializableType.php +++ b/seed/php-model/version/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/version/src/Core/Union.php b/seed/php-model/version/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/version/src/Core/Union.php +++ b/seed/php-model/version/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/version/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/version/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/websocket/src/Core/JsonDecoder.php b/seed/php-model/websocket/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-model/websocket/src/Core/JsonDecoder.php +++ b/seed/php-model/websocket/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-model/websocket/src/Core/JsonDeserializer.php b/seed/php-model/websocket/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-model/websocket/src/Core/JsonDeserializer.php +++ b/seed/php-model/websocket/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-model/websocket/src/Core/JsonSerializer.php b/seed/php-model/websocket/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-model/websocket/src/Core/JsonSerializer.php +++ b/seed/php-model/websocket/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-model/websocket/src/Core/SerializableType.php b/seed/php-model/websocket/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-model/websocket/src/Core/SerializableType.php +++ b/seed/php-model/websocket/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-model/websocket/src/Core/Union.php b/seed/php-model/websocket/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-model/websocket/src/Core/Union.php +++ b/seed/php-model/websocket/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-model/websocket/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/JsonDecoder.php b/seed/php-sdk/alias-extends/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/alias-extends/src/Core/JsonDecoder.php +++ b/seed/php-sdk/alias-extends/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/alias-extends/src/Core/JsonDeserializer.php b/seed/php-sdk/alias-extends/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/alias-extends/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/alias-extends/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/alias-extends/src/Core/JsonSerializer.php b/seed/php-sdk/alias-extends/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/alias-extends/src/Core/JsonSerializer.php +++ b/seed/php-sdk/alias-extends/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/alias-extends/src/Core/SerializableType.php b/seed/php-sdk/alias-extends/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/alias-extends/src/Core/SerializableType.php +++ b/seed/php-sdk/alias-extends/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/alias-extends/src/Core/Union.php b/seed/php-sdk/alias-extends/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/alias-extends/src/Core/Union.php +++ b/seed/php-sdk/alias-extends/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/alias/src/Core/JsonDecoder.php b/seed/php-sdk/alias/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/alias/src/Core/JsonDecoder.php +++ b/seed/php-sdk/alias/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/alias/src/Core/JsonDeserializer.php b/seed/php-sdk/alias/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/alias/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/alias/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/alias/src/Core/JsonSerializer.php b/seed/php-sdk/alias/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/alias/src/Core/JsonSerializer.php +++ b/seed/php-sdk/alias/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/alias/src/Core/SerializableType.php b/seed/php-sdk/alias/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/alias/src/Core/SerializableType.php +++ b/seed/php-sdk/alias/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/alias/src/Core/Union.php b/seed/php-sdk/alias/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/alias/src/Core/Union.php +++ b/seed/php-sdk/alias/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/alias/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/any-auth/src/Core/JsonDecoder.php b/seed/php-sdk/any-auth/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/any-auth/src/Core/JsonDecoder.php +++ b/seed/php-sdk/any-auth/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/any-auth/src/Core/JsonDeserializer.php b/seed/php-sdk/any-auth/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/any-auth/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/any-auth/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/any-auth/src/Core/JsonSerializer.php b/seed/php-sdk/any-auth/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/any-auth/src/Core/JsonSerializer.php +++ b/seed/php-sdk/any-auth/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/any-auth/src/Core/SerializableType.php b/seed/php-sdk/any-auth/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/any-auth/src/Core/SerializableType.php +++ b/seed/php-sdk/any-auth/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/any-auth/src/Core/Union.php b/seed/php-sdk/any-auth/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/any-auth/src/Core/Union.php +++ b/seed/php-sdk/any-auth/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/JsonDecoder.php b/seed/php-sdk/api-wide-base-path/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/api-wide-base-path/src/Core/JsonDecoder.php +++ b/seed/php-sdk/api-wide-base-path/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/api-wide-base-path/src/Core/JsonDeserializer.php b/seed/php-sdk/api-wide-base-path/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/api-wide-base-path/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/api-wide-base-path/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/api-wide-base-path/src/Core/JsonSerializer.php b/seed/php-sdk/api-wide-base-path/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/api-wide-base-path/src/Core/JsonSerializer.php +++ b/seed/php-sdk/api-wide-base-path/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/api-wide-base-path/src/Core/SerializableType.php b/seed/php-sdk/api-wide-base-path/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/api-wide-base-path/src/Core/SerializableType.php +++ b/seed/php-sdk/api-wide-base-path/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Union.php b/seed/php-sdk/api-wide-base-path/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/api-wide-base-path/src/Core/Union.php +++ b/seed/php-sdk/api-wide-base-path/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/audiences/src/Core/JsonDecoder.php b/seed/php-sdk/audiences/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/audiences/src/Core/JsonDecoder.php +++ b/seed/php-sdk/audiences/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/audiences/src/Core/JsonDeserializer.php b/seed/php-sdk/audiences/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/audiences/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/audiences/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/audiences/src/Core/JsonSerializer.php b/seed/php-sdk/audiences/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/audiences/src/Core/JsonSerializer.php +++ b/seed/php-sdk/audiences/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/audiences/src/Core/SerializableType.php b/seed/php-sdk/audiences/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/audiences/src/Core/SerializableType.php +++ b/seed/php-sdk/audiences/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/audiences/src/Core/Union.php b/seed/php-sdk/audiences/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/audiences/src/Core/Union.php +++ b/seed/php-sdk/audiences/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/audiences/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/JsonDecoder.php b/seed/php-sdk/auth-environment-variables/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/auth-environment-variables/src/Core/JsonDecoder.php +++ b/seed/php-sdk/auth-environment-variables/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/auth-environment-variables/src/Core/JsonDeserializer.php b/seed/php-sdk/auth-environment-variables/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/auth-environment-variables/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/auth-environment-variables/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/auth-environment-variables/src/Core/JsonSerializer.php b/seed/php-sdk/auth-environment-variables/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/auth-environment-variables/src/Core/JsonSerializer.php +++ b/seed/php-sdk/auth-environment-variables/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/auth-environment-variables/src/Core/SerializableType.php b/seed/php-sdk/auth-environment-variables/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/auth-environment-variables/src/Core/SerializableType.php +++ b/seed/php-sdk/auth-environment-variables/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Union.php b/seed/php-sdk/auth-environment-variables/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/auth-environment-variables/src/Core/Union.php +++ b/seed/php-sdk/auth-environment-variables/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDecoder.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDecoder.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDeserializer.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonSerializer.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonSerializer.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/SerializableType.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/SerializableType.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Union.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/Union.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/JsonDecoder.php b/seed/php-sdk/basic-auth/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/basic-auth/src/Core/JsonDecoder.php +++ b/seed/php-sdk/basic-auth/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/basic-auth/src/Core/JsonDeserializer.php b/seed/php-sdk/basic-auth/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/basic-auth/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/basic-auth/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/basic-auth/src/Core/JsonSerializer.php b/seed/php-sdk/basic-auth/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/basic-auth/src/Core/JsonSerializer.php +++ b/seed/php-sdk/basic-auth/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/basic-auth/src/Core/SerializableType.php b/seed/php-sdk/basic-auth/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/basic-auth/src/Core/SerializableType.php +++ b/seed/php-sdk/basic-auth/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/basic-auth/src/Core/Union.php b/seed/php-sdk/basic-auth/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/basic-auth/src/Core/Union.php +++ b/seed/php-sdk/basic-auth/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDecoder.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDecoder.php +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDeserializer.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonSerializer.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonSerializer.php +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/SerializableType.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/SerializableType.php +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Union.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/Union.php +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/bytes/src/Core/JsonDecoder.php b/seed/php-sdk/bytes/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/bytes/src/Core/JsonDecoder.php +++ b/seed/php-sdk/bytes/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/bytes/src/Core/JsonDeserializer.php b/seed/php-sdk/bytes/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/bytes/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/bytes/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/bytes/src/Core/JsonSerializer.php b/seed/php-sdk/bytes/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/bytes/src/Core/JsonSerializer.php +++ b/seed/php-sdk/bytes/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/bytes/src/Core/SerializableType.php b/seed/php-sdk/bytes/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/bytes/src/Core/SerializableType.php +++ b/seed/php-sdk/bytes/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/bytes/src/Core/Union.php b/seed/php-sdk/bytes/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/bytes/src/Core/Union.php +++ b/seed/php-sdk/bytes/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/bytes/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/JsonDecoder.php b/seed/php-sdk/circular-references-advanced/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/circular-references-advanced/src/Core/JsonDecoder.php +++ b/seed/php-sdk/circular-references-advanced/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/circular-references-advanced/src/Core/JsonDeserializer.php b/seed/php-sdk/circular-references-advanced/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/circular-references-advanced/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/circular-references-advanced/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/circular-references-advanced/src/Core/JsonSerializer.php b/seed/php-sdk/circular-references-advanced/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/circular-references-advanced/src/Core/JsonSerializer.php +++ b/seed/php-sdk/circular-references-advanced/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/circular-references-advanced/src/Core/SerializableType.php b/seed/php-sdk/circular-references-advanced/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/circular-references-advanced/src/Core/SerializableType.php +++ b/seed/php-sdk/circular-references-advanced/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Union.php b/seed/php-sdk/circular-references-advanced/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/circular-references-advanced/src/Core/Union.php +++ b/seed/php-sdk/circular-references-advanced/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/circular-references/src/Core/JsonDecoder.php b/seed/php-sdk/circular-references/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/circular-references/src/Core/JsonDecoder.php +++ b/seed/php-sdk/circular-references/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/circular-references/src/Core/JsonDeserializer.php b/seed/php-sdk/circular-references/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/circular-references/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/circular-references/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/circular-references/src/Core/JsonSerializer.php b/seed/php-sdk/circular-references/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/circular-references/src/Core/JsonSerializer.php +++ b/seed/php-sdk/circular-references/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/circular-references/src/Core/SerializableType.php b/seed/php-sdk/circular-references/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/circular-references/src/Core/SerializableType.php +++ b/seed/php-sdk/circular-references/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/circular-references/src/Core/Union.php b/seed/php-sdk/circular-references/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/circular-references/src/Core/Union.php +++ b/seed/php-sdk/circular-references/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/JsonDecoder.php b/seed/php-sdk/cross-package-type-names/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/cross-package-type-names/src/Core/JsonDecoder.php +++ b/seed/php-sdk/cross-package-type-names/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/cross-package-type-names/src/Core/JsonDeserializer.php b/seed/php-sdk/cross-package-type-names/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/cross-package-type-names/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/cross-package-type-names/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/cross-package-type-names/src/Core/JsonSerializer.php b/seed/php-sdk/cross-package-type-names/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/cross-package-type-names/src/Core/JsonSerializer.php +++ b/seed/php-sdk/cross-package-type-names/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/cross-package-type-names/src/Core/SerializableType.php b/seed/php-sdk/cross-package-type-names/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/cross-package-type-names/src/Core/SerializableType.php +++ b/seed/php-sdk/cross-package-type-names/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Union.php b/seed/php-sdk/cross-package-type-names/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/cross-package-type-names/src/Core/Union.php +++ b/seed/php-sdk/cross-package-type-names/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/JsonDecoder.php b/seed/php-sdk/custom-auth/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/custom-auth/src/Core/JsonDecoder.php +++ b/seed/php-sdk/custom-auth/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/custom-auth/src/Core/JsonDeserializer.php b/seed/php-sdk/custom-auth/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/custom-auth/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/custom-auth/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/custom-auth/src/Core/JsonSerializer.php b/seed/php-sdk/custom-auth/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/custom-auth/src/Core/JsonSerializer.php +++ b/seed/php-sdk/custom-auth/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/custom-auth/src/Core/SerializableType.php b/seed/php-sdk/custom-auth/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/custom-auth/src/Core/SerializableType.php +++ b/seed/php-sdk/custom-auth/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/custom-auth/src/Core/Union.php b/seed/php-sdk/custom-auth/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/custom-auth/src/Core/Union.php +++ b/seed/php-sdk/custom-auth/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/enum/src/Core/JsonDecoder.php b/seed/php-sdk/enum/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/enum/src/Core/JsonDecoder.php +++ b/seed/php-sdk/enum/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/enum/src/Core/JsonDeserializer.php b/seed/php-sdk/enum/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/enum/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/enum/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/enum/src/Core/JsonSerializer.php b/seed/php-sdk/enum/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/enum/src/Core/JsonSerializer.php +++ b/seed/php-sdk/enum/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/enum/src/Core/SerializableType.php b/seed/php-sdk/enum/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/enum/src/Core/SerializableType.php +++ b/seed/php-sdk/enum/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/enum/src/Core/Union.php b/seed/php-sdk/enum/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/enum/src/Core/Union.php +++ b/seed/php-sdk/enum/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/enum/src/InlinedRequest/Requests/SendEnumInlinedRequest.php b/seed/php-sdk/enum/src/InlinedRequest/Requests/SendEnumInlinedRequest.php index 55b714944c7..eca5e93593c 100644 --- a/seed/php-sdk/enum/src/InlinedRequest/Requests/SendEnumInlinedRequest.php +++ b/seed/php-sdk/enum/src/InlinedRequest/Requests/SendEnumInlinedRequest.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Types\Operand; use Seed\Core\JsonProperty; +use Seed\Types\Color; class SendEnumInlinedRequest extends SerializableType { @@ -21,23 +22,23 @@ class SendEnumInlinedRequest extends SerializableType public ?string $maybeOperand; /** - * @var mixed $operandOrColor + * @var value-of|value-of $operandOrColor */ #[JsonProperty('operandOrColor')] - public mixed $operandOrColor; + public string $operandOrColor; /** - * @var mixed $maybeOperandOrColor + * @var value-of|value-of|null $maybeOperandOrColor */ #[JsonProperty('maybeOperandOrColor')] - public mixed $maybeOperandOrColor; + public string|null $maybeOperandOrColor; /** * @param array{ * operand: value-of, * maybeOperand?: ?value-of, - * operandOrColor: mixed, - * maybeOperandOrColor: mixed, + * operandOrColor: value-of|value-of, + * maybeOperandOrColor?: value-of|value-of|null, * } $values */ public function __construct( @@ -46,6 +47,6 @@ public function __construct( $this->operand = $values['operand']; $this->maybeOperand = $values['maybeOperand'] ?? null; $this->operandOrColor = $values['operandOrColor']; - $this->maybeOperandOrColor = $values['maybeOperandOrColor']; + $this->maybeOperandOrColor = $values['maybeOperandOrColor'] ?? null; } } diff --git a/seed/php-sdk/enum/src/PathParam/PathParamClient.php b/seed/php-sdk/enum/src/PathParam/PathParamClient.php index 621b9958a18..99bacb44266 100644 --- a/seed/php-sdk/enum/src/PathParam/PathParamClient.php +++ b/seed/php-sdk/enum/src/PathParam/PathParamClient.php @@ -4,6 +4,7 @@ use Seed\Core\RawClient; use Seed\Types\Operand; +use Seed\Types\Color; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; use Seed\Core\JsonApiRequest; @@ -29,15 +30,15 @@ public function __construct( /** * @param value-of $operand * @param ?value-of $maybeOperand - * @param mixed $operandOrColor - * @param mixed $maybeOperandOrColor + * @param value-of|value-of $operandOrColor + * @param value-of|value-of|null $maybeOperandOrColor * @param ?array{ * baseUrl?: string, * } $options * @throws SeedException * @throws SeedApiException */ - public function send(string $operand, ?string $maybeOperand = null, mixed $operandOrColor, mixed $maybeOperandOrColor, ?array $options = null): void + public function send(string $operand, ?string $maybeOperand = null, string $operandOrColor, string|null $maybeOperandOrColor = null, ?array $options = null): void { try { $response = $this->client->sendRequest( diff --git a/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumAsQueryParamRequest.php b/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumAsQueryParamRequest.php index b026f8a66f1..d73c5650fb0 100644 --- a/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumAsQueryParamRequest.php +++ b/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumAsQueryParamRequest.php @@ -4,6 +4,7 @@ use Seed\Core\SerializableType; use Seed\Types\Operand; +use Seed\Types\Color; class SendEnumAsQueryParamRequest extends SerializableType { @@ -18,21 +19,21 @@ class SendEnumAsQueryParamRequest extends SerializableType public ?string $maybeOperand; /** - * @var mixed $operandOrColor + * @var value-of|value-of $operandOrColor */ - public mixed $operandOrColor; + public string $operandOrColor; /** - * @var mixed $maybeOperandOrColor + * @var value-of|value-of|null $maybeOperandOrColor */ - public mixed $maybeOperandOrColor; + public string|null $maybeOperandOrColor; /** * @param array{ * operand: value-of, * maybeOperand?: ?value-of, - * operandOrColor: mixed, - * maybeOperandOrColor: mixed, + * operandOrColor: value-of|value-of, + * maybeOperandOrColor?: value-of|value-of|null, * } $values */ public function __construct( @@ -41,6 +42,6 @@ public function __construct( $this->operand = $values['operand']; $this->maybeOperand = $values['maybeOperand'] ?? null; $this->operandOrColor = $values['operandOrColor']; - $this->maybeOperandOrColor = $values['maybeOperandOrColor']; + $this->maybeOperandOrColor = $values['maybeOperandOrColor'] ?? null; } } diff --git a/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumListAsQueryParamRequest.php b/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumListAsQueryParamRequest.php index 5acfab2c4e7..b7c0a49cde0 100644 --- a/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumListAsQueryParamRequest.php +++ b/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumListAsQueryParamRequest.php @@ -4,6 +4,7 @@ use Seed\Core\SerializableType; use Seed\Types\Operand; +use Seed\Types\Color; class SendEnumListAsQueryParamRequest extends SerializableType { @@ -18,12 +19,12 @@ class SendEnumListAsQueryParamRequest extends SerializableType public array $maybeOperand; /** - * @var array $operandOrColor + * @var array|value-of> $operandOrColor */ public array $operandOrColor; /** - * @var array $maybeOperandOrColor + * @var array|value-of|null> $maybeOperandOrColor */ public array $maybeOperandOrColor; @@ -31,8 +32,8 @@ class SendEnumListAsQueryParamRequest extends SerializableType * @param array{ * operand: array>, * maybeOperand: array>, - * operandOrColor: array, - * maybeOperandOrColor: array, + * operandOrColor: array|value-of>, + * maybeOperandOrColor: array|value-of|null>, * } $values */ public function __construct( diff --git a/seed/php-sdk/enum/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/error-property/src/Core/JsonDecoder.php b/seed/php-sdk/error-property/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/error-property/src/Core/JsonDecoder.php +++ b/seed/php-sdk/error-property/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/error-property/src/Core/JsonDeserializer.php b/seed/php-sdk/error-property/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/error-property/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/error-property/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/error-property/src/Core/JsonSerializer.php b/seed/php-sdk/error-property/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/error-property/src/Core/JsonSerializer.php +++ b/seed/php-sdk/error-property/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/error-property/src/Core/SerializableType.php b/seed/php-sdk/error-property/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/error-property/src/Core/SerializableType.php +++ b/seed/php-sdk/error-property/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/error-property/src/Core/Union.php b/seed/php-sdk/error-property/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/error-property/src/Core/Union.php +++ b/seed/php-sdk/error-property/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/error-property/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/examples/src/Core/JsonDecoder.php b/seed/php-sdk/examples/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/examples/src/Core/JsonDecoder.php +++ b/seed/php-sdk/examples/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/examples/src/Core/JsonDeserializer.php b/seed/php-sdk/examples/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/examples/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/examples/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/examples/src/Core/JsonSerializer.php b/seed/php-sdk/examples/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/examples/src/Core/JsonSerializer.php +++ b/seed/php-sdk/examples/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/examples/src/Core/SerializableType.php b/seed/php-sdk/examples/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/examples/src/Core/SerializableType.php +++ b/seed/php-sdk/examples/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/examples/src/Core/Union.php b/seed/php-sdk/examples/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/examples/src/Core/Union.php +++ b/seed/php-sdk/examples/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/examples/src/Types/Identifier.php b/seed/php-sdk/examples/src/Types/Identifier.php index 891355cc1f7..efcb8cc8274 100644 --- a/seed/php-sdk/examples/src/Types/Identifier.php +++ b/seed/php-sdk/examples/src/Types/Identifier.php @@ -8,10 +8,10 @@ class Identifier extends SerializableType { /** - * @var mixed $type + * @var value-of|value-of $type */ #[JsonProperty('type')] - public mixed $type; + public string $type; /** * @var string $value @@ -27,7 +27,7 @@ class Identifier extends SerializableType /** * @param array{ - * type: mixed, + * type: value-of|value-of, * value: string, * label: string, * } $values diff --git a/seed/php-sdk/examples/src/Types/Types/Entity.php b/seed/php-sdk/examples/src/Types/Types/Entity.php index 7316bb6b399..830aef6b2f4 100644 --- a/seed/php-sdk/examples/src/Types/Types/Entity.php +++ b/seed/php-sdk/examples/src/Types/Types/Entity.php @@ -3,15 +3,17 @@ namespace Seed\Types\Types; use Seed\Core\SerializableType; +use Seed\Types\BasicType; +use Seed\Types\ComplexType; use Seed\Core\JsonProperty; class Entity extends SerializableType { /** - * @var mixed $type + * @var value-of|value-of $type */ #[JsonProperty('type')] - public mixed $type; + public string $type; /** * @var string $name @@ -21,7 +23,7 @@ class Entity extends SerializableType /** * @param array{ - * type: mixed, + * type: value-of|value-of, * name: string, * } $values */ diff --git a/seed/php-sdk/examples/src/Types/Types/ResponseType.php b/seed/php-sdk/examples/src/Types/Types/ResponseType.php index 48d032f4eb5..71faed3ceea 100644 --- a/seed/php-sdk/examples/src/Types/Types/ResponseType.php +++ b/seed/php-sdk/examples/src/Types/Types/ResponseType.php @@ -3,19 +3,21 @@ namespace Seed\Types\Types; use Seed\Core\SerializableType; +use Seed\Types\BasicType; +use Seed\Types\ComplexType; use Seed\Core\JsonProperty; class ResponseType extends SerializableType { /** - * @var mixed $type + * @var value-of|value-of $type */ #[JsonProperty('type')] - public mixed $type; + public string $type; /** * @param array{ - * type: mixed, + * type: value-of|value-of, * } $values */ public function __construct( diff --git a/seed/php-sdk/examples/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/JsonDecoder.php b/seed/php-sdk/exhaustive/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/exhaustive/src/Core/JsonDecoder.php +++ b/seed/php-sdk/exhaustive/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/exhaustive/src/Core/JsonDeserializer.php b/seed/php-sdk/exhaustive/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/exhaustive/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/exhaustive/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/exhaustive/src/Core/JsonSerializer.php b/seed/php-sdk/exhaustive/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/exhaustive/src/Core/JsonSerializer.php +++ b/seed/php-sdk/exhaustive/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/exhaustive/src/Core/SerializableType.php b/seed/php-sdk/exhaustive/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/exhaustive/src/Core/SerializableType.php +++ b/seed/php-sdk/exhaustive/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/exhaustive/src/Core/Union.php b/seed/php-sdk/exhaustive/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/exhaustive/src/Core/Union.php +++ b/seed/php-sdk/exhaustive/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/extends/src/Core/JsonDecoder.php b/seed/php-sdk/extends/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/extends/src/Core/JsonDecoder.php +++ b/seed/php-sdk/extends/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/extends/src/Core/JsonDeserializer.php b/seed/php-sdk/extends/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/extends/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/extends/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/extends/src/Core/JsonSerializer.php b/seed/php-sdk/extends/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/extends/src/Core/JsonSerializer.php +++ b/seed/php-sdk/extends/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/extends/src/Core/SerializableType.php b/seed/php-sdk/extends/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/extends/src/Core/SerializableType.php +++ b/seed/php-sdk/extends/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/extends/src/Core/Union.php b/seed/php-sdk/extends/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/extends/src/Core/Union.php +++ b/seed/php-sdk/extends/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/extends/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/JsonDecoder.php b/seed/php-sdk/extra-properties/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/extra-properties/src/Core/JsonDecoder.php +++ b/seed/php-sdk/extra-properties/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/extra-properties/src/Core/JsonDeserializer.php b/seed/php-sdk/extra-properties/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/extra-properties/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/extra-properties/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/extra-properties/src/Core/JsonSerializer.php b/seed/php-sdk/extra-properties/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/extra-properties/src/Core/JsonSerializer.php +++ b/seed/php-sdk/extra-properties/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/extra-properties/src/Core/SerializableType.php b/seed/php-sdk/extra-properties/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/extra-properties/src/Core/SerializableType.php +++ b/seed/php-sdk/extra-properties/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/extra-properties/src/Core/Union.php b/seed/php-sdk/extra-properties/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/extra-properties/src/Core/Union.php +++ b/seed/php-sdk/extra-properties/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/file-download/src/Core/JsonDecoder.php b/seed/php-sdk/file-download/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/file-download/src/Core/JsonDecoder.php +++ b/seed/php-sdk/file-download/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/file-download/src/Core/JsonDeserializer.php b/seed/php-sdk/file-download/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/file-download/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/file-download/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/file-download/src/Core/JsonSerializer.php b/seed/php-sdk/file-download/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/file-download/src/Core/JsonSerializer.php +++ b/seed/php-sdk/file-download/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/file-download/src/Core/SerializableType.php b/seed/php-sdk/file-download/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/file-download/src/Core/SerializableType.php +++ b/seed/php-sdk/file-download/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/file-download/src/Core/Union.php b/seed/php-sdk/file-download/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/file-download/src/Core/Union.php +++ b/seed/php-sdk/file-download/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/file-download/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/file-upload/src/Core/JsonDecoder.php b/seed/php-sdk/file-upload/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/file-upload/src/Core/JsonDecoder.php +++ b/seed/php-sdk/file-upload/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/file-upload/src/Core/JsonDeserializer.php b/seed/php-sdk/file-upload/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/file-upload/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/file-upload/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/file-upload/src/Core/JsonSerializer.php b/seed/php-sdk/file-upload/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/file-upload/src/Core/JsonSerializer.php +++ b/seed/php-sdk/file-upload/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/file-upload/src/Core/SerializableType.php b/seed/php-sdk/file-upload/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/file-upload/src/Core/SerializableType.php +++ b/seed/php-sdk/file-upload/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/file-upload/src/Core/Union.php b/seed/php-sdk/file-upload/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/file-upload/src/Core/Union.php +++ b/seed/php-sdk/file-upload/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/folders/src/Core/JsonDecoder.php b/seed/php-sdk/folders/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/folders/src/Core/JsonDecoder.php +++ b/seed/php-sdk/folders/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/folders/src/Core/JsonDeserializer.php b/seed/php-sdk/folders/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/folders/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/folders/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/folders/src/Core/JsonSerializer.php b/seed/php-sdk/folders/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/folders/src/Core/JsonSerializer.php +++ b/seed/php-sdk/folders/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/folders/src/Core/SerializableType.php b/seed/php-sdk/folders/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/folders/src/Core/SerializableType.php +++ b/seed/php-sdk/folders/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/folders/src/Core/Union.php b/seed/php-sdk/folders/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/folders/src/Core/Union.php +++ b/seed/php-sdk/folders/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/folders/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDecoder.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDecoder.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDeserializer.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonSerializer.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonSerializer.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/SerializableType.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/SerializableType.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Union.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Union.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php index ebd3d19a264..2882f1b0477 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class DeleteRequest extends SerializableType { @@ -27,25 +28,25 @@ class DeleteRequest extends SerializableType public ?string $namespace; /** - * @var mixed $filter + * @var array|array|null $filter */ - #[JsonProperty('filter')] - public mixed $filter; + #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $filter; /** * @param array{ * ids?: ?array, * deleteAll?: ?bool, * namespace?: ?string, - * filter: mixed, + * filter?: array|array|null, * } $values */ public function __construct( - array $values, + array $values = [], ) { $this->ids = $values['ids'] ?? null; $this->deleteAll = $values['deleteAll'] ?? null; $this->namespace = $values['namespace'] ?? null; - $this->filter = $values['filter']; + $this->filter = $values['filter'] ?? null; } } diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php index 7219ecab76c..0706e7ce2ff 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php @@ -4,23 +4,24 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; +use Seed\Core\Union; class DescribeRequest extends SerializableType { /** - * @var mixed $filter + * @var array|array|null $filter */ - #[JsonProperty('filter')] - public mixed $filter; + #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $filter; /** * @param array{ - * filter: mixed, + * filter?: array|array|null, * } $values */ public function __construct( - array $values, + array $values = [], ) { - $this->filter = $values['filter']; + $this->filter = $values['filter'] ?? null; } } diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php index 9fd063c8cff..88f2351d064 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php @@ -4,6 +4,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; +use Seed\Core\Union; use Seed\Types\QueryColumn; use Seed\Core\ArrayType; use Seed\Types\IndexedData; @@ -23,10 +24,10 @@ class QueryRequest extends SerializableType public int $topK; /** - * @var mixed $filter + * @var array|array|null $filter */ - #[JsonProperty('filter')] - public mixed $filter; + #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $filter; /** * @var ?bool $includeValues @@ -68,7 +69,7 @@ class QueryRequest extends SerializableType * @param array{ * namespace?: ?string, * topK: int, - * filter: mixed, + * filter?: array|array|null, * includeValues?: ?bool, * includeMetadata?: ?bool, * queries?: ?array, @@ -82,7 +83,7 @@ public function __construct( ) { $this->namespace = $values['namespace'] ?? null; $this->topK = $values['topK']; - $this->filter = $values['filter']; + $this->filter = $values['filter'] ?? null; $this->includeValues = $values['includeValues'] ?? null; $this->includeMetadata = $values['includeMetadata'] ?? null; $this->queries = $values['queries'] ?? null; diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php index 7e2afca26af..21a4c59b468 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; use Seed\Types\IndexedData; class UpdateRequest extends SerializableType @@ -22,10 +23,10 @@ class UpdateRequest extends SerializableType public ?array $values; /** - * @var mixed $setMetadata + * @var array|array|null $setMetadata */ - #[JsonProperty('setMetadata')] - public mixed $setMetadata; + #[JsonProperty('setMetadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $setMetadata; /** * @var ?string $namespace @@ -43,7 +44,7 @@ class UpdateRequest extends SerializableType * @param array{ * id: string, * values?: ?array, - * setMetadata: mixed, + * setMetadata?: array|array|null, * namespace?: ?string, * indexedData?: ?IndexedData, * } $values @@ -53,7 +54,7 @@ public function __construct( ) { $this->id = $values['id']; $this->values = $values['values'] ?? null; - $this->setMetadata = $values['setMetadata']; + $this->setMetadata = $values['setMetadata'] ?? null; $this->namespace = $values['namespace'] ?? null; $this->indexedData = $values['indexedData'] ?? null; } diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php index c5e2615ac05..c696c3d3bc3 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class Column extends SerializableType { @@ -21,10 +22,10 @@ class Column extends SerializableType public array $values; /** - * @var mixed $metadata + * @var array|array|null $metadata */ - #[JsonProperty('metadata')] - public mixed $metadata; + #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $metadata; /** * @var ?IndexedData $indexedData @@ -36,7 +37,7 @@ class Column extends SerializableType * @param array{ * id: string, * values: array, - * metadata: mixed, + * metadata?: array|array|null, * indexedData?: ?IndexedData, * } $values */ @@ -45,7 +46,7 @@ public function __construct( ) { $this->id = $values['id']; $this->values = $values['values']; - $this->metadata = $values['metadata']; + $this->metadata = $values['metadata'] ?? null; $this->indexedData = $values['indexedData'] ?? null; } } diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php index 9e23e5acb30..27e8ae75d26 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class QueryColumn extends SerializableType { @@ -27,10 +28,10 @@ class QueryColumn extends SerializableType public ?string $namespace; /** - * @var mixed $filter + * @var array|array|null $filter */ - #[JsonProperty('filter')] - public mixed $filter; + #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $filter; /** * @var ?IndexedData $indexedData @@ -43,7 +44,7 @@ class QueryColumn extends SerializableType * values: array, * topK?: ?int, * namespace?: ?string, - * filter: mixed, + * filter?: array|array|null, * indexedData?: ?IndexedData, * } $values */ @@ -53,7 +54,7 @@ public function __construct( $this->values = $values['values']; $this->topK = $values['topK'] ?? null; $this->namespace = $values['namespace'] ?? null; - $this->filter = $values['filter']; + $this->filter = $values['filter'] ?? null; $this->indexedData = $values['indexedData'] ?? null; } } diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php index e770ca3b3e9..9c0399df337 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class ScoredColumn extends SerializableType { @@ -27,10 +28,10 @@ class ScoredColumn extends SerializableType public ?array $values; /** - * @var mixed $metadata + * @var array|array|null $metadata */ - #[JsonProperty('metadata')] - public mixed $metadata; + #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $metadata; /** * @var ?IndexedData $indexedData @@ -43,7 +44,7 @@ class ScoredColumn extends SerializableType * id: string, * score?: ?float, * values?: ?array, - * metadata: mixed, + * metadata?: array|array|null, * indexedData?: ?IndexedData, * } $values */ @@ -53,7 +54,7 @@ public function __construct( $this->id = $values['id']; $this->score = $values['score'] ?? null; $this->values = $values['values'] ?? null; - $this->metadata = $values['metadata']; + $this->metadata = $values['metadata'] ?? null; $this->indexedData = $values['indexedData'] ?? null; } } diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/JsonDecoder.php b/seed/php-sdk/grpc-proto/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/grpc-proto/src/Core/JsonDecoder.php +++ b/seed/php-sdk/grpc-proto/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/grpc-proto/src/Core/JsonDeserializer.php b/seed/php-sdk/grpc-proto/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/grpc-proto/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/grpc-proto/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/grpc-proto/src/Core/JsonSerializer.php b/seed/php-sdk/grpc-proto/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/grpc-proto/src/Core/JsonSerializer.php +++ b/seed/php-sdk/grpc-proto/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/grpc-proto/src/Core/SerializableType.php b/seed/php-sdk/grpc-proto/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/grpc-proto/src/Core/SerializableType.php +++ b/seed/php-sdk/grpc-proto/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/grpc-proto/src/Core/Union.php b/seed/php-sdk/grpc-proto/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/grpc-proto/src/Core/Union.php +++ b/seed/php-sdk/grpc-proto/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/grpc-proto/src/Types/UserModel.php b/seed/php-sdk/grpc-proto/src/Types/UserModel.php index aa7089f7af2..cff03d8ec8c 100644 --- a/seed/php-sdk/grpc-proto/src/Types/UserModel.php +++ b/seed/php-sdk/grpc-proto/src/Types/UserModel.php @@ -4,6 +4,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; +use Seed\Core\Union; class UserModel extends SerializableType { @@ -32,10 +33,10 @@ class UserModel extends SerializableType public ?float $weight; /** - * @var mixed $metadata + * @var array|array|null $metadata */ - #[JsonProperty('metadata')] - public mixed $metadata; + #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $metadata; /** * @param array{ @@ -43,16 +44,16 @@ class UserModel extends SerializableType * email?: ?string, * age?: ?int, * weight?: ?float, - * metadata: mixed, + * metadata?: array|array|null, * } $values */ public function __construct( - array $values, + array $values = [], ) { $this->username = $values['username'] ?? null; $this->email = $values['email'] ?? null; $this->age = $values['age'] ?? null; $this->weight = $values['weight'] ?? null; - $this->metadata = $values['metadata']; + $this->metadata = $values['metadata'] ?? null; } } diff --git a/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php b/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php index ba5b06a57f3..a239f9835bc 100644 --- a/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php +++ b/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php @@ -4,6 +4,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; +use Seed\Core\Union; class CreateRequest extends SerializableType { @@ -32,10 +33,10 @@ class CreateRequest extends SerializableType public ?float $weight; /** - * @var mixed $metadata + * @var array|array|null $metadata */ - #[JsonProperty('metadata')] - public mixed $metadata; + #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])] + public array|null $metadata; /** * @param array{ @@ -43,16 +44,16 @@ class CreateRequest extends SerializableType * email?: ?string, * age?: ?int, * weight?: ?float, - * metadata: mixed, + * metadata?: array|array|null, * } $values */ public function __construct( - array $values, + array $values = [], ) { $this->username = $values['username'] ?? null; $this->email = $values['email'] ?? null; $this->age = $values['age'] ?? null; $this->weight = $values['weight'] ?? null; - $this->metadata = $values['metadata']; + $this->metadata = $values['metadata'] ?? null; } } diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/JsonDecoder.php b/seed/php-sdk/idempotency-headers/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/idempotency-headers/src/Core/JsonDecoder.php +++ b/seed/php-sdk/idempotency-headers/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/idempotency-headers/src/Core/JsonDeserializer.php b/seed/php-sdk/idempotency-headers/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/idempotency-headers/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/idempotency-headers/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/idempotency-headers/src/Core/JsonSerializer.php b/seed/php-sdk/idempotency-headers/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/idempotency-headers/src/Core/JsonSerializer.php +++ b/seed/php-sdk/idempotency-headers/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/idempotency-headers/src/Core/SerializableType.php b/seed/php-sdk/idempotency-headers/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/idempotency-headers/src/Core/SerializableType.php +++ b/seed/php-sdk/idempotency-headers/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/idempotency-headers/src/Core/Union.php b/seed/php-sdk/idempotency-headers/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/idempotency-headers/src/Core/Union.php +++ b/seed/php-sdk/idempotency-headers/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/imdb/src/Core/JsonDecoder.php b/seed/php-sdk/imdb/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/imdb/src/Core/JsonDecoder.php +++ b/seed/php-sdk/imdb/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/imdb/src/Core/JsonDeserializer.php b/seed/php-sdk/imdb/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/imdb/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/imdb/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/imdb/src/Core/JsonSerializer.php b/seed/php-sdk/imdb/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/imdb/src/Core/JsonSerializer.php +++ b/seed/php-sdk/imdb/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/imdb/src/Core/SerializableType.php b/seed/php-sdk/imdb/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/imdb/src/Core/SerializableType.php +++ b/seed/php-sdk/imdb/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/imdb/src/Core/Union.php b/seed/php-sdk/imdb/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/imdb/src/Core/Union.php +++ b/seed/php-sdk/imdb/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/imdb/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/literal/src/Core/JsonDecoder.php b/seed/php-sdk/literal/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/literal/src/Core/JsonDecoder.php +++ b/seed/php-sdk/literal/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/literal/src/Core/JsonDeserializer.php b/seed/php-sdk/literal/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/literal/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/literal/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/literal/src/Core/JsonSerializer.php b/seed/php-sdk/literal/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/literal/src/Core/JsonSerializer.php +++ b/seed/php-sdk/literal/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/literal/src/Core/SerializableType.php b/seed/php-sdk/literal/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/literal/src/Core/SerializableType.php +++ b/seed/php-sdk/literal/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/literal/src/Core/Union.php b/seed/php-sdk/literal/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/literal/src/Core/Union.php +++ b/seed/php-sdk/literal/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/literal/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/JsonDecoder.php b/seed/php-sdk/mixed-case/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/mixed-case/src/Core/JsonDecoder.php +++ b/seed/php-sdk/mixed-case/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/mixed-case/src/Core/JsonDeserializer.php b/seed/php-sdk/mixed-case/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/mixed-case/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/mixed-case/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/mixed-case/src/Core/JsonSerializer.php b/seed/php-sdk/mixed-case/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/mixed-case/src/Core/JsonSerializer.php +++ b/seed/php-sdk/mixed-case/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/mixed-case/src/Core/SerializableType.php b/seed/php-sdk/mixed-case/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/mixed-case/src/Core/SerializableType.php +++ b/seed/php-sdk/mixed-case/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/mixed-case/src/Core/Union.php b/seed/php-sdk/mixed-case/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/mixed-case/src/Core/Union.php +++ b/seed/php-sdk/mixed-case/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/JsonDecoder.php b/seed/php-sdk/mixed-file-directory/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/mixed-file-directory/src/Core/JsonDecoder.php +++ b/seed/php-sdk/mixed-file-directory/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/mixed-file-directory/src/Core/JsonDeserializer.php b/seed/php-sdk/mixed-file-directory/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/mixed-file-directory/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/mixed-file-directory/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/mixed-file-directory/src/Core/JsonSerializer.php b/seed/php-sdk/mixed-file-directory/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/mixed-file-directory/src/Core/JsonSerializer.php +++ b/seed/php-sdk/mixed-file-directory/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/mixed-file-directory/src/Core/SerializableType.php b/seed/php-sdk/mixed-file-directory/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/mixed-file-directory/src/Core/SerializableType.php +++ b/seed/php-sdk/mixed-file-directory/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Union.php b/seed/php-sdk/mixed-file-directory/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/mixed-file-directory/src/Core/Union.php +++ b/seed/php-sdk/mixed-file-directory/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/JsonDecoder.php b/seed/php-sdk/multi-line-docs/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/multi-line-docs/src/Core/JsonDecoder.php +++ b/seed/php-sdk/multi-line-docs/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/multi-line-docs/src/Core/JsonDeserializer.php b/seed/php-sdk/multi-line-docs/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/multi-line-docs/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/multi-line-docs/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/multi-line-docs/src/Core/JsonSerializer.php b/seed/php-sdk/multi-line-docs/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/multi-line-docs/src/Core/JsonSerializer.php +++ b/seed/php-sdk/multi-line-docs/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/multi-line-docs/src/Core/SerializableType.php b/seed/php-sdk/multi-line-docs/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/multi-line-docs/src/Core/SerializableType.php +++ b/seed/php-sdk/multi-line-docs/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/multi-line-docs/src/Core/Union.php b/seed/php-sdk/multi-line-docs/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/multi-line-docs/src/Core/Union.php +++ b/seed/php-sdk/multi-line-docs/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/no-environment/src/Core/JsonDecoder.php b/seed/php-sdk/no-environment/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/no-environment/src/Core/JsonDecoder.php +++ b/seed/php-sdk/no-environment/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/no-environment/src/Core/JsonDeserializer.php b/seed/php-sdk/no-environment/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/no-environment/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/no-environment/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/no-environment/src/Core/JsonSerializer.php b/seed/php-sdk/no-environment/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/no-environment/src/Core/JsonSerializer.php +++ b/seed/php-sdk/no-environment/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/no-environment/src/Core/SerializableType.php b/seed/php-sdk/no-environment/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/no-environment/src/Core/SerializableType.php +++ b/seed/php-sdk/no-environment/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/no-environment/src/Core/Union.php b/seed/php-sdk/no-environment/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/no-environment/src/Core/Union.php +++ b/seed/php-sdk/no-environment/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDecoder.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonSerializer.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonSerializer.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/SerializableType.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/SerializableType.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Union.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/Union.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/SerializableType.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/SerializableType.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Union.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Union.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/SerializableType.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/SerializableType.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Union.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Union.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Core/JsonDecoder.php +++ b/seed/php-sdk/oauth-client-credentials/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/oauth-client-credentials/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/JsonSerializer.php b/seed/php-sdk/oauth-client-credentials/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Core/JsonSerializer.php +++ b/seed/php-sdk/oauth-client-credentials/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/SerializableType.php b/seed/php-sdk/oauth-client-credentials/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Core/SerializableType.php +++ b/seed/php-sdk/oauth-client-credentials/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Union.php b/seed/php-sdk/oauth-client-credentials/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Core/Union.php +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/object/src/Core/JsonDecoder.php b/seed/php-sdk/object/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/object/src/Core/JsonDecoder.php +++ b/seed/php-sdk/object/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/object/src/Core/JsonDeserializer.php b/seed/php-sdk/object/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/object/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/object/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/object/src/Core/JsonSerializer.php b/seed/php-sdk/object/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/object/src/Core/JsonSerializer.php +++ b/seed/php-sdk/object/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/object/src/Core/SerializableType.php b/seed/php-sdk/object/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/object/src/Core/SerializableType.php +++ b/seed/php-sdk/object/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/object/src/Core/Union.php b/seed/php-sdk/object/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/object/src/Core/Union.php +++ b/seed/php-sdk/object/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/object/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/JsonDecoder.php b/seed/php-sdk/objects-with-imports/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/objects-with-imports/src/Core/JsonDecoder.php +++ b/seed/php-sdk/objects-with-imports/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/objects-with-imports/src/Core/JsonDeserializer.php b/seed/php-sdk/objects-with-imports/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/objects-with-imports/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/objects-with-imports/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/objects-with-imports/src/Core/JsonSerializer.php b/seed/php-sdk/objects-with-imports/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/objects-with-imports/src/Core/JsonSerializer.php +++ b/seed/php-sdk/objects-with-imports/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/objects-with-imports/src/Core/SerializableType.php b/seed/php-sdk/objects-with-imports/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/objects-with-imports/src/Core/SerializableType.php +++ b/seed/php-sdk/objects-with-imports/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/objects-with-imports/src/Core/Union.php b/seed/php-sdk/objects-with-imports/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/objects-with-imports/src/Core/Union.php +++ b/seed/php-sdk/objects-with-imports/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/optional/src/Core/JsonDecoder.php b/seed/php-sdk/optional/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/optional/src/Core/JsonDecoder.php +++ b/seed/php-sdk/optional/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/optional/src/Core/JsonDeserializer.php b/seed/php-sdk/optional/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/optional/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/optional/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/optional/src/Core/JsonSerializer.php b/seed/php-sdk/optional/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/optional/src/Core/JsonSerializer.php +++ b/seed/php-sdk/optional/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/optional/src/Core/SerializableType.php b/seed/php-sdk/optional/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/optional/src/Core/SerializableType.php +++ b/seed/php-sdk/optional/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/optional/src/Core/Union.php b/seed/php-sdk/optional/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/optional/src/Core/Union.php +++ b/seed/php-sdk/optional/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/optional/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/package-yml/src/Core/JsonDecoder.php b/seed/php-sdk/package-yml/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/package-yml/src/Core/JsonDecoder.php +++ b/seed/php-sdk/package-yml/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/package-yml/src/Core/JsonDeserializer.php b/seed/php-sdk/package-yml/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/package-yml/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/package-yml/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/package-yml/src/Core/JsonSerializer.php b/seed/php-sdk/package-yml/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/package-yml/src/Core/JsonSerializer.php +++ b/seed/php-sdk/package-yml/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/package-yml/src/Core/SerializableType.php b/seed/php-sdk/package-yml/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/package-yml/src/Core/SerializableType.php +++ b/seed/php-sdk/package-yml/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/package-yml/src/Core/Union.php b/seed/php-sdk/package-yml/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/package-yml/src/Core/Union.php +++ b/seed/php-sdk/package-yml/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/pagination/src/Core/JsonDecoder.php b/seed/php-sdk/pagination/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/pagination/src/Core/JsonDecoder.php +++ b/seed/php-sdk/pagination/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/pagination/src/Core/JsonDeserializer.php b/seed/php-sdk/pagination/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/pagination/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/pagination/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/pagination/src/Core/JsonSerializer.php b/seed/php-sdk/pagination/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/pagination/src/Core/JsonSerializer.php +++ b/seed/php-sdk/pagination/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/pagination/src/Core/SerializableType.php b/seed/php-sdk/pagination/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/pagination/src/Core/SerializableType.php +++ b/seed/php-sdk/pagination/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/pagination/src/Core/Union.php b/seed/php-sdk/pagination/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/pagination/src/Core/Union.php +++ b/seed/php-sdk/pagination/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/pagination/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/plain-text/src/Core/JsonDecoder.php b/seed/php-sdk/plain-text/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/plain-text/src/Core/JsonDecoder.php +++ b/seed/php-sdk/plain-text/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/plain-text/src/Core/JsonDeserializer.php b/seed/php-sdk/plain-text/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/plain-text/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/plain-text/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/plain-text/src/Core/JsonSerializer.php b/seed/php-sdk/plain-text/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/plain-text/src/Core/JsonSerializer.php +++ b/seed/php-sdk/plain-text/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/plain-text/src/Core/SerializableType.php b/seed/php-sdk/plain-text/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/plain-text/src/Core/SerializableType.php +++ b/seed/php-sdk/plain-text/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/plain-text/src/Core/Union.php b/seed/php-sdk/plain-text/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/plain-text/src/Core/Union.php +++ b/seed/php-sdk/plain-text/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/JsonDecoder.php b/seed/php-sdk/query-parameters/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/query-parameters/src/Core/JsonDecoder.php +++ b/seed/php-sdk/query-parameters/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/query-parameters/src/Core/JsonDeserializer.php b/seed/php-sdk/query-parameters/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/query-parameters/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/query-parameters/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/query-parameters/src/Core/JsonSerializer.php b/seed/php-sdk/query-parameters/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/query-parameters/src/Core/JsonSerializer.php +++ b/seed/php-sdk/query-parameters/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/query-parameters/src/Core/SerializableType.php b/seed/php-sdk/query-parameters/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/query-parameters/src/Core/SerializableType.php +++ b/seed/php-sdk/query-parameters/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/query-parameters/src/Core/Union.php b/seed/php-sdk/query-parameters/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/query-parameters/src/Core/Union.php +++ b/seed/php-sdk/query-parameters/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/JsonDecoder.php b/seed/php-sdk/reserved-keywords/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/reserved-keywords/src/Core/JsonDecoder.php +++ b/seed/php-sdk/reserved-keywords/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/reserved-keywords/src/Core/JsonDeserializer.php b/seed/php-sdk/reserved-keywords/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/reserved-keywords/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/reserved-keywords/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/reserved-keywords/src/Core/JsonSerializer.php b/seed/php-sdk/reserved-keywords/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/reserved-keywords/src/Core/JsonSerializer.php +++ b/seed/php-sdk/reserved-keywords/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/reserved-keywords/src/Core/SerializableType.php b/seed/php-sdk/reserved-keywords/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/reserved-keywords/src/Core/SerializableType.php +++ b/seed/php-sdk/reserved-keywords/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/reserved-keywords/src/Core/Union.php b/seed/php-sdk/reserved-keywords/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/reserved-keywords/src/Core/Union.php +++ b/seed/php-sdk/reserved-keywords/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/response-property/src/Core/JsonDecoder.php b/seed/php-sdk/response-property/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/response-property/src/Core/JsonDecoder.php +++ b/seed/php-sdk/response-property/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/response-property/src/Core/JsonDeserializer.php b/seed/php-sdk/response-property/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/response-property/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/response-property/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/response-property/src/Core/JsonSerializer.php b/seed/php-sdk/response-property/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/response-property/src/Core/JsonSerializer.php +++ b/seed/php-sdk/response-property/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/response-property/src/Core/SerializableType.php b/seed/php-sdk/response-property/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/response-property/src/Core/SerializableType.php +++ b/seed/php-sdk/response-property/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/response-property/src/Core/Union.php b/seed/php-sdk/response-property/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/response-property/src/Core/Union.php +++ b/seed/php-sdk/response-property/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/response-property/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/.github/workflows/ci.yml b/seed/php-sdk/server-sent-event-examples/.github/workflows/ci.yml new file mode 100644 index 00000000000..258bf33a19f --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test \ No newline at end of file diff --git a/seed/php-sdk/server-sent-event-examples/.gitignore b/seed/php-sdk/server-sent-event-examples/.gitignore new file mode 100644 index 00000000000..f38efc46ade --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/.gitignore @@ -0,0 +1,4 @@ +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-sdk/server-sent-event-examples/.mock/definition/api.yml b/seed/php-sdk/server-sent-event-examples/.mock/definition/api.yml new file mode 100644 index 00000000000..80e84c41785 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/.mock/definition/api.yml @@ -0,0 +1 @@ +name: server-sent-events diff --git a/seed/php-sdk/server-sent-event-examples/.mock/definition/completions.yml b/seed/php-sdk/server-sent-event-examples/.mock/definition/completions.yml new file mode 100644 index 00000000000..09a88253331 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/.mock/definition/completions.yml @@ -0,0 +1,36 @@ +types: + StreamedCompletion: + properties: + delta: string + tokens: optional + +service: + auth: false + base-path: "" + endpoints: + stream: + method: POST + path: /stream + request: + name: StreamCompletionRequest + body: + properties: + query: string + response-stream: + type: StreamedCompletion + format: sse + terminator: "[[DONE]]" + examples: + - name: "Stream completions" + request: + query: "foo" + response: + stream: + - event: discriminant-1 + data: + delta: "foo" + tokens: 1 + - event: discriminant-2 + data: + delta: "bar" + tokens: 2 diff --git a/seed/php-sdk/server-sent-event-examples/.mock/fern.config.json b/seed/php-sdk/server-sent-event-examples/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/php-sdk/server-sent-event-examples/.mock/generators.yml b/seed/php-sdk/server-sent-event-examples/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/php-sdk/server-sent-event-examples/composer.json b/seed/php-sdk/server-sent-event-examples/composer.json new file mode 100644 index 00000000000..7f5821806d4 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/composer.json @@ -0,0 +1,40 @@ + +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.9" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "\\Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src" + } +} diff --git a/seed/php-sdk/server-sent-event-examples/phpstan.neon b/seed/php-sdk/server-sent-event-examples/phpstan.neon new file mode 100644 index 00000000000..29a11a92a19 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-sdk/server-sent-event-examples/phpunit.xml b/seed/php-sdk/server-sent-event-examples/phpunit.xml new file mode 100644 index 00000000000..54630a51163 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-sdk/server-sent-event-examples/snippet-templates.json b/seed/php-sdk/server-sent-event-examples/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-sdk/server-sent-event-examples/snippet.json b/seed/php-sdk/server-sent-event-examples/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-sdk/server-sent-event-examples/src/Completions/CompletionsClient.php b/seed/php-sdk/server-sent-event-examples/src/Completions/CompletionsClient.php new file mode 100644 index 00000000000..2b145a52d09 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Completions/CompletionsClient.php @@ -0,0 +1,58 @@ +client = $client; + } + + /** + * @param StreamCompletionRequest $request + * @param ?array{ + * baseUrl?: string, + * } $options + * @throws SeedException + * @throws SeedApiException + */ + public function stream(StreamCompletionRequest $request, ?array $options = null): void + { + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? '', + path: "stream", + method: HttpMethod::POST, + body: $request, + ), + ); + $statusCode = $response->getStatusCode(); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Completions/Requests/StreamCompletionRequest.php b/seed/php-sdk/server-sent-event-examples/src/Completions/Requests/StreamCompletionRequest.php new file mode 100644 index 00000000000..2023705caff --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Completions/Requests/StreamCompletionRequest.php @@ -0,0 +1,26 @@ +query = $values['query']; + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Completions/Types/StreamedCompletion.php b/seed/php-sdk/server-sent-event-examples/src/Completions/Types/StreamedCompletion.php new file mode 100644 index 00000000000..0573b87281c --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Completions/Types/StreamedCompletion.php @@ -0,0 +1,34 @@ +delta = $values['delta']; + $this->tokens = $values['tokens'] ?? null; + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/ArrayType.php b/seed/php-sdk/server-sent-event-examples/src/Core/ArrayType.php new file mode 100644 index 00000000000..b2ed8bf12b2 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/BaseApiRequest.php b/seed/php-sdk/server-sent-event-examples/src/Core/BaseApiRequest.php new file mode 100644 index 00000000000..2ace034ec90 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Constant.php b/seed/php-sdk/server-sent-event-examples/src/Core/Constant.php new file mode 100644 index 00000000000..abbac7f6649 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Constant.php @@ -0,0 +1,12 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/JsonDecoder.php b/seed/php-sdk/server-sent-event-examples/src/Core/JsonDecoder.php new file mode 100644 index 00000000000..c7f9629e018 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/JsonDecoder.php @@ -0,0 +1,160 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/JsonDeserializer.php b/seed/php-sdk/server-sent-event-examples/src/Core/JsonDeserializer.php new file mode 100644 index 00000000000..b1de7d141ac --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/JsonDeserializer.php @@ -0,0 +1,202 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/JsonEncoder.php b/seed/php-sdk/server-sent-event-examples/src/Core/JsonEncoder.php new file mode 100644 index 00000000000..ba5191a8068 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/RawClient.php b/seed/php-sdk/server-sent-event-examples/src/Core/RawClient.php new file mode 100644 index 00000000000..1c0e42bf650 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/RawClient.php @@ -0,0 +1,138 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/SerializableType.php b/seed/php-sdk/server-sent-event-examples/src/Core/SerializableType.php new file mode 100644 index 00000000000..9121bdca01c --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/SerializableType.php @@ -0,0 +1,179 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Union.php b/seed/php-sdk/server-sent-event-examples/src/Core/Union.php new file mode 100644 index 00000000000..1e9fe801ee7 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Union.php @@ -0,0 +1,62 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Utils.php b/seed/php-sdk/server-sent-event-examples/src/Core/Utils.php new file mode 100644 index 00000000000..74416068d02 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Exceptions/SeedApiException.php b/seed/php-sdk/server-sent-event-examples/src/Exceptions/SeedApiException.php new file mode 100644 index 00000000000..41a85392b70 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Exceptions/SeedApiException.php @@ -0,0 +1,53 @@ +body = $body; + parent::__construct($message, $statusCode, $previous); + } + + /** + * Returns the body of the response that triggered the exception. + * + * @return mixed + */ + public function getBody(): mixed + { + return $this->body; + } + + /** + * @return string + */ + public function __toString(): string + { + if (empty($this->body)) { + return "$this->message; Status Code: $this->code\n"; + } + return "$this->message; Status Code: $this->code; Body: " . $this->body . "\n"; + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Exceptions/SeedException.php b/seed/php-sdk/server-sent-event-examples/src/Exceptions/SeedException.php new file mode 100644 index 00000000000..45703527673 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Exceptions/SeedException.php @@ -0,0 +1,12 @@ +, + * } $options + */ + private ?array $options; + + /** + * @var RawClient $client + */ + private RawClient $client; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + ?array $options = null, + ) { + $defaultHeaders = [ + 'X-Fern-Language' => 'PHP', + 'X-Fern-SDK-Name' => 'Seed', + 'X-Fern-SDK-Version' => '0.0.1', + ]; + + $this->options = $options ?? []; + $this->options['headers'] = array_merge( + $defaultHeaders, + $this->options['headers'] ?? [], + ); + + $this->client = new RawClient( + options: $this->options, + ); + + $this->completions = new CompletionsClient($this->client); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d93afc9e44 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php new file mode 100644 index 00000000000..b44f3d093e6 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EnumTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EnumTest.php new file mode 100644 index 00000000000..ef5b8484dfd --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php new file mode 100644 index 00000000000..67bfd235b2f --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..3bf18aec25b --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..4667ecafcb9 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php new file mode 100644 index 00000000000..134296f56e3 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php new file mode 100644 index 00000000000..bf6345e5c6f --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/RawClientTest.php new file mode 100644 index 00000000000..e01ae63b41a --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php new file mode 100644 index 00000000000..899e949836c --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php new file mode 100644 index 00000000000..8e7ca1b825c --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php new file mode 100644 index 00000000000..8d0998f4b7e --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/server-sent-events/.github/workflows/ci.yml b/seed/php-sdk/server-sent-events/.github/workflows/ci.yml new file mode 100644 index 00000000000..258bf33a19f --- /dev/null +++ b/seed/php-sdk/server-sent-events/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test \ No newline at end of file diff --git a/seed/php-sdk/server-sent-events/.gitignore b/seed/php-sdk/server-sent-events/.gitignore new file mode 100644 index 00000000000..f38efc46ade --- /dev/null +++ b/seed/php-sdk/server-sent-events/.gitignore @@ -0,0 +1,4 @@ +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-sdk/server-sent-events/.mock/definition/api.yml b/seed/php-sdk/server-sent-events/.mock/definition/api.yml new file mode 100644 index 00000000000..80e84c41785 --- /dev/null +++ b/seed/php-sdk/server-sent-events/.mock/definition/api.yml @@ -0,0 +1 @@ +name: server-sent-events diff --git a/seed/php-sdk/server-sent-events/.mock/definition/completions.yml b/seed/php-sdk/server-sent-events/.mock/definition/completions.yml new file mode 100644 index 00000000000..d1748fad19e --- /dev/null +++ b/seed/php-sdk/server-sent-events/.mock/definition/completions.yml @@ -0,0 +1,22 @@ +types: + StreamedCompletion: + properties: + delta: string + tokens: optional + +service: + auth: false + base-path: "" + endpoints: + stream: + method: POST + path: /stream + request: + name: StreamCompletionRequest + body: + properties: + query: string + response-stream: + type: StreamedCompletion + format: sse + terminator: "[[DONE]]" diff --git a/seed/php-sdk/server-sent-events/.mock/fern.config.json b/seed/php-sdk/server-sent-events/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/php-sdk/server-sent-events/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/php-sdk/server-sent-events/.mock/generators.yml b/seed/php-sdk/server-sent-events/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/php-sdk/server-sent-events/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/php-sdk/server-sent-events/composer.json b/seed/php-sdk/server-sent-events/composer.json new file mode 100644 index 00000000000..7f5821806d4 --- /dev/null +++ b/seed/php-sdk/server-sent-events/composer.json @@ -0,0 +1,40 @@ + +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.9" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "\\Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src" + } +} diff --git a/seed/php-sdk/server-sent-events/phpstan.neon b/seed/php-sdk/server-sent-events/phpstan.neon new file mode 100644 index 00000000000..29a11a92a19 --- /dev/null +++ b/seed/php-sdk/server-sent-events/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-sdk/server-sent-events/phpunit.xml b/seed/php-sdk/server-sent-events/phpunit.xml new file mode 100644 index 00000000000..54630a51163 --- /dev/null +++ b/seed/php-sdk/server-sent-events/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-sdk/server-sent-events/snippet-templates.json b/seed/php-sdk/server-sent-events/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-sdk/server-sent-events/snippet.json b/seed/php-sdk/server-sent-events/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-sdk/server-sent-events/src/Completions/CompletionsClient.php b/seed/php-sdk/server-sent-events/src/Completions/CompletionsClient.php new file mode 100644 index 00000000000..2b145a52d09 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Completions/CompletionsClient.php @@ -0,0 +1,58 @@ +client = $client; + } + + /** + * @param StreamCompletionRequest $request + * @param ?array{ + * baseUrl?: string, + * } $options + * @throws SeedException + * @throws SeedApiException + */ + public function stream(StreamCompletionRequest $request, ?array $options = null): void + { + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? '', + path: "stream", + method: HttpMethod::POST, + body: $request, + ), + ); + $statusCode = $response->getStatusCode(); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Completions/Requests/StreamCompletionRequest.php b/seed/php-sdk/server-sent-events/src/Completions/Requests/StreamCompletionRequest.php new file mode 100644 index 00000000000..2023705caff --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Completions/Requests/StreamCompletionRequest.php @@ -0,0 +1,26 @@ +query = $values['query']; + } +} diff --git a/seed/php-sdk/server-sent-events/src/Completions/Types/StreamedCompletion.php b/seed/php-sdk/server-sent-events/src/Completions/Types/StreamedCompletion.php new file mode 100644 index 00000000000..0573b87281c --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Completions/Types/StreamedCompletion.php @@ -0,0 +1,34 @@ +delta = $values['delta']; + $this->tokens = $values['tokens'] ?? null; + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/ArrayType.php b/seed/php-sdk/server-sent-events/src/Core/ArrayType.php new file mode 100644 index 00000000000..b2ed8bf12b2 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/BaseApiRequest.php b/seed/php-sdk/server-sent-events/src/Core/BaseApiRequest.php new file mode 100644 index 00000000000..2ace034ec90 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Constant.php b/seed/php-sdk/server-sent-events/src/Core/Constant.php new file mode 100644 index 00000000000..abbac7f6649 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Constant.php @@ -0,0 +1,12 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/JsonDecoder.php b/seed/php-sdk/server-sent-events/src/Core/JsonDecoder.php new file mode 100644 index 00000000000..c7f9629e018 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/JsonDecoder.php @@ -0,0 +1,160 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/JsonDeserializer.php b/seed/php-sdk/server-sent-events/src/Core/JsonDeserializer.php new file mode 100644 index 00000000000..b1de7d141ac --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/JsonDeserializer.php @@ -0,0 +1,202 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/JsonEncoder.php b/seed/php-sdk/server-sent-events/src/Core/JsonEncoder.php new file mode 100644 index 00000000000..ba5191a8068 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/RawClient.php b/seed/php-sdk/server-sent-events/src/Core/RawClient.php new file mode 100644 index 00000000000..1c0e42bf650 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/RawClient.php @@ -0,0 +1,138 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/SerializableType.php b/seed/php-sdk/server-sent-events/src/Core/SerializableType.php new file mode 100644 index 00000000000..9121bdca01c --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/SerializableType.php @@ -0,0 +1,179 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Union.php b/seed/php-sdk/server-sent-events/src/Core/Union.php new file mode 100644 index 00000000000..1e9fe801ee7 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Union.php @@ -0,0 +1,62 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Utils.php b/seed/php-sdk/server-sent-events/src/Core/Utils.php new file mode 100644 index 00000000000..74416068d02 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/server-sent-events/src/Exceptions/SeedApiException.php b/seed/php-sdk/server-sent-events/src/Exceptions/SeedApiException.php new file mode 100644 index 00000000000..41a85392b70 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Exceptions/SeedApiException.php @@ -0,0 +1,53 @@ +body = $body; + parent::__construct($message, $statusCode, $previous); + } + + /** + * Returns the body of the response that triggered the exception. + * + * @return mixed + */ + public function getBody(): mixed + { + return $this->body; + } + + /** + * @return string + */ + public function __toString(): string + { + if (empty($this->body)) { + return "$this->message; Status Code: $this->code\n"; + } + return "$this->message; Status Code: $this->code; Body: " . $this->body . "\n"; + } +} diff --git a/seed/php-sdk/server-sent-events/src/Exceptions/SeedException.php b/seed/php-sdk/server-sent-events/src/Exceptions/SeedException.php new file mode 100644 index 00000000000..45703527673 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Exceptions/SeedException.php @@ -0,0 +1,12 @@ +, + * } $options + */ + private ?array $options; + + /** + * @var RawClient $client + */ + private RawClient $client; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + ?array $options = null, + ) { + $defaultHeaders = [ + 'X-Fern-Language' => 'PHP', + 'X-Fern-SDK-Name' => 'Seed', + 'X-Fern-SDK-Version' => '0.0.1', + ]; + + $this->options = $options ?? []; + $this->options['headers'] = array_merge( + $defaultHeaders, + $this->options['headers'] ?? [], + ); + + $this->client = new RawClient( + options: $this->options, + ); + + $this->completions = new CompletionsClient($this->client); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d93afc9e44 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/EmptyArraysTest.php new file mode 100644 index 00000000000..b44f3d093e6 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/EnumTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/EnumTest.php new file mode 100644 index 00000000000..ef5b8484dfd --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/InvalidTypesTest.php new file mode 100644 index 00000000000..67bfd235b2f --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..3bf18aec25b --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..4667ecafcb9 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php new file mode 100644 index 00000000000..134296f56e3 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php new file mode 100644 index 00000000000..bf6345e5c6f --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/RawClientTest.php new file mode 100644 index 00000000000..e01ae63b41a --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/ScalarTypesTest.php new file mode 100644 index 00000000000..899e949836c --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/TestTypeTest.php new file mode 100644 index 00000000000..8e7ca1b825c --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php new file mode 100644 index 00000000000..8d0998f4b7e --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/JsonDecoder.php b/seed/php-sdk/simple-fhir/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/simple-fhir/src/Core/JsonDecoder.php +++ b/seed/php-sdk/simple-fhir/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/simple-fhir/src/Core/JsonDeserializer.php b/seed/php-sdk/simple-fhir/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/simple-fhir/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/simple-fhir/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/simple-fhir/src/Core/JsonSerializer.php b/seed/php-sdk/simple-fhir/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/simple-fhir/src/Core/JsonSerializer.php +++ b/seed/php-sdk/simple-fhir/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/simple-fhir/src/Core/SerializableType.php b/seed/php-sdk/simple-fhir/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/simple-fhir/src/Core/SerializableType.php +++ b/seed/php-sdk/simple-fhir/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/simple-fhir/src/Core/Union.php b/seed/php-sdk/simple-fhir/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/simple-fhir/src/Core/Union.php +++ b/seed/php-sdk/simple-fhir/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/simple-fhir/src/Types/BaseResource.php b/seed/php-sdk/simple-fhir/src/Types/BaseResource.php index 505b309bd0d..83ac40eb598 100644 --- a/seed/php-sdk/simple-fhir/src/Types/BaseResource.php +++ b/seed/php-sdk/simple-fhir/src/Types/BaseResource.php @@ -5,6 +5,7 @@ use Seed\Core\SerializableType; use Seed\Core\JsonProperty; use Seed\Core\ArrayType; +use Seed\Core\Union; class BaseResource extends SerializableType { @@ -15,9 +16,9 @@ class BaseResource extends SerializableType public string $id; /** - * @var array $relatedResources + * @var array $relatedResources */ - #[JsonProperty('related_resources'), ArrayType(['mixed'])] + #[JsonProperty('related_resources'), ArrayType([new Union(Account::class, Patient::class, Practitioner::class, Script::class)])] public array $relatedResources; /** @@ -29,7 +30,7 @@ class BaseResource extends SerializableType /** * @param array{ * id: string, - * relatedResources: array, + * relatedResources: array, * memo: Memo, * } $values */ diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/JsonDecoder.php b/seed/php-sdk/single-url-environment-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/single-url-environment-default/src/Core/JsonDecoder.php +++ b/seed/php-sdk/single-url-environment-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/single-url-environment-default/src/Core/JsonDeserializer.php b/seed/php-sdk/single-url-environment-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/single-url-environment-default/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/single-url-environment-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/single-url-environment-default/src/Core/JsonSerializer.php b/seed/php-sdk/single-url-environment-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/single-url-environment-default/src/Core/JsonSerializer.php +++ b/seed/php-sdk/single-url-environment-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/single-url-environment-default/src/Core/SerializableType.php b/seed/php-sdk/single-url-environment-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/single-url-environment-default/src/Core/SerializableType.php +++ b/seed/php-sdk/single-url-environment-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Union.php b/seed/php-sdk/single-url-environment-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/single-url-environment-default/src/Core/Union.php +++ b/seed/php-sdk/single-url-environment-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDecoder.php b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDecoder.php +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDeserializer.php b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonSerializer.php b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonSerializer.php +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/SerializableType.php b/seed/php-sdk/single-url-environment-no-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/SerializableType.php +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Union.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/Union.php +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/JsonDecoder.php b/seed/php-sdk/streaming-parameter/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/streaming-parameter/src/Core/JsonDecoder.php +++ b/seed/php-sdk/streaming-parameter/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/streaming-parameter/src/Core/JsonDeserializer.php b/seed/php-sdk/streaming-parameter/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/streaming-parameter/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/streaming-parameter/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/streaming-parameter/src/Core/JsonSerializer.php b/seed/php-sdk/streaming-parameter/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/streaming-parameter/src/Core/JsonSerializer.php +++ b/seed/php-sdk/streaming-parameter/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/streaming-parameter/src/Core/SerializableType.php b/seed/php-sdk/streaming-parameter/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/streaming-parameter/src/Core/SerializableType.php +++ b/seed/php-sdk/streaming-parameter/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/streaming-parameter/src/Core/Union.php b/seed/php-sdk/streaming-parameter/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/streaming-parameter/src/Core/Union.php +++ b/seed/php-sdk/streaming-parameter/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/streaming/src/Core/JsonDecoder.php b/seed/php-sdk/streaming/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/streaming/src/Core/JsonDecoder.php +++ b/seed/php-sdk/streaming/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/streaming/src/Core/JsonDeserializer.php b/seed/php-sdk/streaming/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/streaming/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/streaming/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/streaming/src/Core/JsonSerializer.php b/seed/php-sdk/streaming/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/streaming/src/Core/JsonSerializer.php +++ b/seed/php-sdk/streaming/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/streaming/src/Core/SerializableType.php b/seed/php-sdk/streaming/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/streaming/src/Core/SerializableType.php +++ b/seed/php-sdk/streaming/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/streaming/src/Core/Union.php b/seed/php-sdk/streaming/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/streaming/src/Core/Union.php +++ b/seed/php-sdk/streaming/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/streaming/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/trace/src/Core/JsonDecoder.php b/seed/php-sdk/trace/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/trace/src/Core/JsonDecoder.php +++ b/seed/php-sdk/trace/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/trace/src/Core/JsonDeserializer.php b/seed/php-sdk/trace/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/trace/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/trace/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/trace/src/Core/JsonSerializer.php b/seed/php-sdk/trace/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/trace/src/Core/JsonSerializer.php +++ b/seed/php-sdk/trace/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/trace/src/Core/SerializableType.php b/seed/php-sdk/trace/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/trace/src/Core/SerializableType.php +++ b/seed/php-sdk/trace/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/trace/src/Core/Union.php b/seed/php-sdk/trace/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/trace/src/Core/Union.php +++ b/seed/php-sdk/trace/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/trace/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/JsonDecoder.php b/seed/php-sdk/undiscriminated-unions/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/undiscriminated-unions/src/Core/JsonDecoder.php +++ b/seed/php-sdk/undiscriminated-unions/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/JsonDeserializer.php b/seed/php-sdk/undiscriminated-unions/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/undiscriminated-unions/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/undiscriminated-unions/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/JsonSerializer.php b/seed/php-sdk/undiscriminated-unions/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/undiscriminated-unions/src/Core/JsonSerializer.php +++ b/seed/php-sdk/undiscriminated-unions/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/SerializableType.php b/seed/php-sdk/undiscriminated-unions/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/undiscriminated-unions/src/Core/SerializableType.php +++ b/seed/php-sdk/undiscriminated-unions/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Union.php b/seed/php-sdk/undiscriminated-unions/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/undiscriminated-unions/src/Core/Union.php +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/undiscriminated-unions/src/Union/UnionClient.php b/seed/php-sdk/undiscriminated-unions/src/Union/UnionClient.php index fe592455cbc..035279c7d31 100644 --- a/seed/php-sdk/undiscriminated-unions/src/Union/UnionClient.php +++ b/seed/php-sdk/undiscriminated-unions/src/Union/UnionClient.php @@ -7,9 +7,12 @@ use Seed\Exceptions\SeedApiException; use Seed\Core\JsonApiRequest; use Seed\Core\HttpMethod; +use Seed\Core\JsonSerializer; +use Seed\Core\Union; use Seed\Core\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; +use Seed\Union\Types\KeyType; class UnionClient { @@ -28,15 +31,15 @@ public function __construct( } /** - * @param mixed $request + * @param string|array|int|array|array> $request * @param ?array{ * baseUrl?: string, * } $options - * @return mixed + * @return string|array|int|array|array> * @throws SeedException * @throws SeedApiException */ - public function get(mixed $request, ?array $options = null): mixed + public function get(string|array|int $request, ?array $options = null): string|array|int { try { $response = $this->client->sendRequest( @@ -44,13 +47,13 @@ public function get(mixed $request, ?array $options = null): mixed baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? '', path: "", method: HttpMethod::POST, - body: $request, + body: JsonSerializer::serializeUnion($request, new Union('string', ['string'], 'integer', ['integer'], [['integer']])), ), ); $statusCode = $response->getStatusCode(); if ($statusCode >= 200 && $statusCode < 400) { $json = $response->getBody()->getContents(); - return JsonDecoder::decodeMixed($json); + return JsonDecoder::decodeUnion($json, new Union('string', ['string'], 'integer', ['integer'], [['integer']])); // @phpstan-ignore-line } } catch (JsonException $e) { throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); @@ -68,7 +71,7 @@ public function get(mixed $request, ?array $options = null): mixed * @param ?array{ * baseUrl?: string, * } $options - * @return array + * @return array|string, string> * @throws SeedException * @throws SeedApiException */ @@ -85,7 +88,7 @@ public function getMetadata(?array $options = null): array $statusCode = $response->getStatusCode(); if ($statusCode >= 200 && $statusCode < 400) { $json = $response->getBody()->getContents(); - return JsonDecoder::decodeArray($json, ['mixed' => 'string']); // @phpstan-ignore-line + return JsonDecoder::decodeArray($json, ['string' => 'string']); // @phpstan-ignore-line } } catch (JsonException $e) { throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/unions/src/Core/JsonDecoder.php b/seed/php-sdk/unions/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/unions/src/Core/JsonDecoder.php +++ b/seed/php-sdk/unions/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/unions/src/Core/JsonDeserializer.php b/seed/php-sdk/unions/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/unions/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/unions/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/unions/src/Core/JsonSerializer.php b/seed/php-sdk/unions/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/unions/src/Core/JsonSerializer.php +++ b/seed/php-sdk/unions/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/unions/src/Core/SerializableType.php b/seed/php-sdk/unions/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/unions/src/Core/SerializableType.php +++ b/seed/php-sdk/unions/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/unions/src/Core/Union.php b/seed/php-sdk/unions/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/unions/src/Core/Union.php +++ b/seed/php-sdk/unions/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/unions/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/unknown/src/Core/JsonDecoder.php b/seed/php-sdk/unknown/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/unknown/src/Core/JsonDecoder.php +++ b/seed/php-sdk/unknown/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/unknown/src/Core/JsonDeserializer.php b/seed/php-sdk/unknown/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/unknown/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/unknown/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/unknown/src/Core/JsonSerializer.php b/seed/php-sdk/unknown/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/unknown/src/Core/JsonSerializer.php +++ b/seed/php-sdk/unknown/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/unknown/src/Core/SerializableType.php b/seed/php-sdk/unknown/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/unknown/src/Core/SerializableType.php +++ b/seed/php-sdk/unknown/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/unknown/src/Core/Union.php b/seed/php-sdk/unknown/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/unknown/src/Core/Union.php +++ b/seed/php-sdk/unknown/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/unknown/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/validation/src/Core/JsonDecoder.php b/seed/php-sdk/validation/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/validation/src/Core/JsonDecoder.php +++ b/seed/php-sdk/validation/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/validation/src/Core/JsonDeserializer.php b/seed/php-sdk/validation/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/validation/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/validation/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/validation/src/Core/JsonSerializer.php b/seed/php-sdk/validation/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/validation/src/Core/JsonSerializer.php +++ b/seed/php-sdk/validation/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/validation/src/Core/SerializableType.php b/seed/php-sdk/validation/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/validation/src/Core/SerializableType.php +++ b/seed/php-sdk/validation/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/validation/src/Core/Union.php b/seed/php-sdk/validation/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/validation/src/Core/Union.php +++ b/seed/php-sdk/validation/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/validation/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/variables/src/Core/JsonDecoder.php b/seed/php-sdk/variables/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/variables/src/Core/JsonDecoder.php +++ b/seed/php-sdk/variables/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/variables/src/Core/JsonDeserializer.php b/seed/php-sdk/variables/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/variables/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/variables/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/variables/src/Core/JsonSerializer.php b/seed/php-sdk/variables/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/variables/src/Core/JsonSerializer.php +++ b/seed/php-sdk/variables/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/variables/src/Core/SerializableType.php b/seed/php-sdk/variables/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/variables/src/Core/SerializableType.php +++ b/seed/php-sdk/variables/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/variables/src/Core/Union.php b/seed/php-sdk/variables/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/variables/src/Core/Union.php +++ b/seed/php-sdk/variables/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/JsonDecoder.php b/seed/php-sdk/version-no-default/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/version-no-default/src/Core/JsonDecoder.php +++ b/seed/php-sdk/version-no-default/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/version-no-default/src/Core/JsonDeserializer.php b/seed/php-sdk/version-no-default/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/version-no-default/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/version-no-default/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/version-no-default/src/Core/JsonSerializer.php b/seed/php-sdk/version-no-default/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/version-no-default/src/Core/JsonSerializer.php +++ b/seed/php-sdk/version-no-default/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/version-no-default/src/Core/SerializableType.php b/seed/php-sdk/version-no-default/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/version-no-default/src/Core/SerializableType.php +++ b/seed/php-sdk/version-no-default/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/version-no-default/src/Core/Union.php b/seed/php-sdk/version-no-default/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/version-no-default/src/Core/Union.php +++ b/seed/php-sdk/version-no-default/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/version/src/Core/JsonDecoder.php b/seed/php-sdk/version/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/version/src/Core/JsonDecoder.php +++ b/seed/php-sdk/version/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/version/src/Core/JsonDeserializer.php b/seed/php-sdk/version/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/version/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/version/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/version/src/Core/JsonSerializer.php b/seed/php-sdk/version/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/version/src/Core/JsonSerializer.php +++ b/seed/php-sdk/version/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/version/src/Core/SerializableType.php b/seed/php-sdk/version/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/version/src/Core/SerializableType.php +++ b/seed/php-sdk/version/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/version/src/Core/Union.php b/seed/php-sdk/version/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/version/src/Core/Union.php +++ b/seed/php-sdk/version/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/version/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/websocket/src/Core/JsonDecoder.php b/seed/php-sdk/websocket/src/Core/JsonDecoder.php index 34651d0b9eb..c7f9629e018 100644 --- a/seed/php-sdk/websocket/src/Core/JsonDecoder.php +++ b/seed/php-sdk/websocket/src/Core/JsonDecoder.php @@ -121,6 +121,19 @@ public static function decodeArray(string $json, array $type): array return JsonDeserializer::deserializeArray($decoded, $type); } + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } /** * Decodes a JSON string and returns a mixed. * diff --git a/seed/php-sdk/websocket/src/Core/JsonDeserializer.php b/seed/php-sdk/websocket/src/Core/JsonDeserializer.php index 4c9998886ee..b1de7d141ac 100644 --- a/seed/php-sdk/websocket/src/Core/JsonDeserializer.php +++ b/seed/php-sdk/websocket/src/Core/JsonDeserializer.php @@ -66,18 +66,9 @@ public static function deserializeArray(array $data, array $type): array private static function deserializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::deserializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); + return self::deserializeUnion($data, $type); } + if (is_array($type)) { return self::deserializeArray((array)$data, $type); } @@ -85,9 +76,33 @@ private static function deserializeValue(mixed $data, mixed $type): mixed if (gettype($type) != "string") { throw new JsonException("Unexpected non-string type."); } + return self::deserializeSingleValue($data, $type); } + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + /** * Deserializes a single value based on its expected type. * diff --git a/seed/php-sdk/websocket/src/Core/JsonSerializer.php b/seed/php-sdk/websocket/src/Core/JsonSerializer.php index eae0c087441..1e37550f15e 100644 --- a/seed/php-sdk/websocket/src/Core/JsonSerializer.php +++ b/seed/php-sdk/websocket/src/Core/JsonSerializer.php @@ -57,14 +57,7 @@ public static function serializeArray(array $data, array $type): array private static function serializeValue(mixed $data, mixed $type): mixed { if ($type instanceof Union) { - foreach ($type->types as $unionType) { - try { - return self::serializeSingleValue($data, $unionType); - } catch (Exception) { - continue; - } - } - throw new JsonException("Cannot serialize value with any of the union types."); + return self::serializeUnion($data, $type); } if (is_array($type)) { @@ -78,6 +71,30 @@ private static function serializeValue(mixed $data, mixed $type): mixed return self::serializeSingleValue($data, $type); } + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + /** * Serializes a single value based on its type. * diff --git a/seed/php-sdk/websocket/src/Core/SerializableType.php b/seed/php-sdk/websocket/src/Core/SerializableType.php index ecb6c6abc19..9121bdca01c 100644 --- a/seed/php-sdk/websocket/src/Core/SerializableType.php +++ b/seed/php-sdk/websocket/src/Core/SerializableType.php @@ -56,6 +56,13 @@ public function jsonSerialize(): array : JsonSerializer::serializeDateTime($value); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + // Handle arrays with type annotations $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; if ($arrayTypeAttr && is_array($value)) { @@ -135,6 +142,13 @@ public static function jsonDeserialize(array $data): static $value = JsonDeserializer::deserializeArray($value, $arrayType); } + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + // Handle object $type = $property->getType(); if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { diff --git a/seed/php-sdk/websocket/src/Core/Union.php b/seed/php-sdk/websocket/src/Core/Union.php index 8608d2cae49..1e9fe801ee7 100644 --- a/seed/php-sdk/websocket/src/Core/Union.php +++ b/seed/php-sdk/websocket/src/Core/Union.php @@ -2,20 +2,61 @@ namespace Seed\Core; +use Attribute; + +/** + * Union type attribute for flexible type declarations. + * + * This class is used to define a union of multiple types for a property. + * It allows a property to accept one of several types, including strings, arrays, or other Union types. + * This class supports complex types, nested unions, and arrays, providing a flexible mechanism for property type validation and serialization. + * + * Example: + * + * ```php + * #[Union('string', 'int', 'null', new Union('boolean', 'float'))] + * private mixed $property; + * ``` + */ +#[Attribute(Attribute::TARGET_PROPERTY)] class Union { /** - * @var string[] + * @var array> The types allowed for this property, which can be strings, arrays, or nested Union types. */ public array $types; - public function __construct(string ...$strings) + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) { - $this->types = $strings; + $this->types = $types; } + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ public function __toString(): string { - return implode(' | ', $this->types); + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); } } diff --git a/seed/php-sdk/websocket/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..e278eb42883 --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} From 980a66d6a986d6ce036a54979f0ba67ca1dbebd5 Mon Sep 17 00:00:00 2001 From: Alex McKinney Date: Wed, 2 Oct 2024 11:44:13 -0400 Subject: [PATCH 04/10] chore(php): Organize core utility layout (#4791) --- generators/commons/src/project/File.ts | 7 +- generators/php/codegen/src/AsIs.ts | 68 +++--- .../{ => Client}/BaseApiRequest.Template.php | 0 .../asIs/{ => Client}/HttpMethod.Template.php | 0 .../asIs/{ => Client}/RawClient.Template.php | 1 + .../{ => Client}/RawClientTest.Template.php | 8 +- .../{ => Json}/DateArrayTypeTest.Template.php | 8 +- .../{ => Json}/EmptyArraysTest.Template.php | 8 +- .../src/asIs/{ => Json}/EnumTest.Template.php | 6 +- .../{ => Json}/InvalidTypesTest.Template.php | 4 +- .../{ => Json}/JsonApiRequest.Template.php | 2 + .../asIs/{ => Json}/JsonDecoder.Template.php | 1 + .../{ => Json}/JsonDeserializer.Template.php | 4 +- .../asIs/{ => Json}/JsonEncoder.Template.php | 0 .../asIs/{ => Json}/JsonProperty.Template.php | 0 .../{ => Json}/JsonSerializer.Template.php | 4 +- .../MixedDateArrayTypeTest.Template.php | 10 +- .../NestedUnionArrayTypeTest.Template.php | 12 +- .../NullPropertyTypeTest.Template.php | 4 +- .../NullableArrayTypeTest.Template.php | 8 +- .../{ => Json}/ScalarTypesTest.Template.php | 8 +- .../{ => Json}/SerializableType.Template.php | 5 +- .../asIs/{ => Json}/TestTypeTest.Template.php | 12 +- .../UnionArrayTypeTest.Template.php | 8 +- .../UnionPropertyTypeTest.Template.php | 6 +- .../src/asIs/{ => Json}/Utils.Template.php | 2 +- .../asIs/{ => Types}/ArrayType.Template.php | 0 .../asIs/{ => Types}/Constant.Template.php | 0 .../asIs/{ => Types}/DateType.Template.php | 0 .../src/asIs/{ => Types}/Union.Template.php | 2 +- .../context/AbstractPhpGeneratorContext.ts | 54 ++++- .../php/codegen/src/project/PhpProject.ts | 11 +- generators/php/sdk/src/SdkGeneratorContext.ts | 8 +- generators/php/sdk/src/core/RawClient.ts | 2 +- seed/php-model/alias-extends/src/Child.php | 4 +- .../alias-extends/src/Core/ArrayType.php | 16 -- .../alias-extends/src/Core/Constant.php | 12 -- .../alias-extends/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../alias-extends/src/Core/Json/Utils.php | 61 ++++++ .../alias-extends/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../alias-extends/src/Core/JsonEncoder.php | 20 -- .../alias-extends/src/Core/JsonProperty.php | 13 -- .../alias-extends/src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../alias-extends/src/Core/Types/Constant.php | 12 ++ .../alias-extends/src/Core/Types/DateType.php | 16 ++ .../alias-extends/src/Core/Types/Union.php | 62 ++++++ .../alias-extends/src/Core/Union.php | 62 ------ .../alias-extends/src/Core/Utils.php | 61 ------ seed/php-model/alias-extends/src/Parent_.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/alias/src/Core/ArrayType.php | 16 -- seed/php-model/alias/src/Core/Constant.php | 12 -- seed/php-model/alias/src/Core/DateType.php | 16 -- .../alias/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../alias/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../alias/src/Core/Json/JsonEncoder.php | 20 ++ .../alias/src/Core/Json/JsonProperty.php | 13 ++ .../alias/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../alias/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-model/alias/src/Core/Json/Utils.php | 61 ++++++ seed/php-model/alias/src/Core/JsonDecoder.php | 160 -------------- .../alias/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-model/alias/src/Core/JsonEncoder.php | 20 -- .../php-model/alias/src/Core/JsonProperty.php | 13 -- .../alias/src/Core/JsonSerializer.php | 190 ---------------- .../alias/src/Core/SerializableType.php | 179 --------------- .../alias/src/Core/Types/ArrayType.php | 16 ++ .../alias/src/Core/Types/Constant.php | 12 ++ .../alias/src/Core/Types/DateType.php | 16 ++ seed/php-model/alias/src/Core/Types/Union.php | 62 ++++++ seed/php-model/alias/src/Core/Union.php | 62 ------ seed/php-model/alias/src/Core/Utils.php | 61 ------ seed/php-model/alias/src/Type.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../alias/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../alias/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../alias/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../alias/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../alias/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../any-auth/src/Auth/TokenResponse.php | 4 +- .../php-model/any-auth/src/Core/ArrayType.php | 16 -- seed/php-model/any-auth/src/Core/Constant.php | 12 -- seed/php-model/any-auth/src/Core/DateType.php | 16 -- .../any-auth/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../any-auth/src/Core/Json/JsonEncoder.php | 20 ++ .../any-auth/src/Core/Json/JsonProperty.php | 13 ++ .../any-auth/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../any-auth/src/Core/Json/Utils.php | 61 ++++++ .../any-auth/src/Core/JsonDecoder.php | 160 -------------- .../any-auth/src/Core/JsonDeserializer.php | 202 ----------------- .../any-auth/src/Core/JsonEncoder.php | 20 -- .../any-auth/src/Core/JsonProperty.php | 13 -- .../any-auth/src/Core/JsonSerializer.php | 190 ---------------- .../any-auth/src/Core/SerializableType.php | 179 --------------- .../any-auth/src/Core/Types/ArrayType.php | 16 ++ .../any-auth/src/Core/Types/Constant.php | 12 ++ .../any-auth/src/Core/Types/DateType.php | 16 ++ .../any-auth/src/Core/Types/Union.php | 62 ++++++ seed/php-model/any-auth/src/Core/Union.php | 62 ------ seed/php-model/any-auth/src/Core/Utils.php | 61 ------ seed/php-model/any-auth/src/User/User.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../any-auth/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../any-auth/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../api-wide-base-path/src/Core/ArrayType.php | 16 -- .../api-wide-base-path/src/Core/Constant.php | 12 -- .../api-wide-base-path/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../api-wide-base-path/src/Core/Union.php | 62 ------ .../api-wide-base-path/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../audiences/src/Core/ArrayType.php | 16 -- .../php-model/audiences/src/Core/Constant.php | 12 -- .../php-model/audiences/src/Core/DateType.php | 16 -- .../audiences/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../audiences/src/Core/Json/JsonEncoder.php | 20 ++ .../audiences/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../audiences/src/Core/Json/Utils.php | 61 ++++++ .../audiences/src/Core/JsonDecoder.php | 160 -------------- .../audiences/src/Core/JsonDeserializer.php | 202 ----------------- .../audiences/src/Core/JsonEncoder.php | 20 -- .../audiences/src/Core/JsonProperty.php | 13 -- .../audiences/src/Core/JsonSerializer.php | 190 ---------------- .../audiences/src/Core/SerializableType.php | 179 --------------- .../audiences/src/Core/Types/ArrayType.php | 16 ++ .../audiences/src/Core/Types/Constant.php | 12 ++ .../audiences/src/Core/Types/DateType.php | 16 ++ .../audiences/src/Core/Types/Union.php | 62 ++++++ seed/php-model/audiences/src/Core/Union.php | 62 ------ seed/php-model/audiences/src/Core/Utils.php | 61 ------ .../src/FolderA/Service/Response.php | 4 +- .../audiences/src/FolderB/Common/Foo.php | 4 +- .../src/FolderC/Common/FolderCFoo.php | 4 +- .../src/FolderD/Service/Response.php | 4 +- .../audiences/src/Foo/FilteredType.php | 4 +- .../audiences/src/Foo/ImportingType.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../audiences/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../Errors/UnauthorizedRequestErrorBody.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../basic-auth/src/Core/ArrayType.php | 16 -- .../basic-auth/src/Core/Constant.php | 12 -- .../basic-auth/src/Core/DateType.php | 16 -- .../basic-auth/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../basic-auth/src/Core/Json/JsonEncoder.php | 20 ++ .../basic-auth/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../basic-auth/src/Core/Json/Utils.php | 61 ++++++ .../basic-auth/src/Core/JsonDecoder.php | 160 -------------- .../basic-auth/src/Core/JsonDeserializer.php | 202 ----------------- .../basic-auth/src/Core/JsonEncoder.php | 20 -- .../basic-auth/src/Core/JsonProperty.php | 13 -- .../basic-auth/src/Core/JsonSerializer.php | 190 ---------------- .../basic-auth/src/Core/SerializableType.php | 179 --------------- .../basic-auth/src/Core/Types/ArrayType.php | 16 ++ .../basic-auth/src/Core/Types/Constant.php | 12 ++ .../basic-auth/src/Core/Types/DateType.php | 16 ++ .../basic-auth/src/Core/Types/Union.php | 62 ++++++ seed/php-model/basic-auth/src/Core/Union.php | 62 ------ seed/php-model/basic-auth/src/Core/Utils.php | 61 ------ .../Errors/UnauthorizedRequestErrorBody.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../basic-auth/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/bytes/src/Core/ArrayType.php | 16 -- seed/php-model/bytes/src/Core/Constant.php | 12 -- seed/php-model/bytes/src/Core/DateType.php | 16 -- .../bytes/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../bytes/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../bytes/src/Core/Json/JsonEncoder.php | 20 ++ .../bytes/src/Core/Json/JsonProperty.php | 13 ++ .../bytes/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../bytes/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-model/bytes/src/Core/Json/Utils.php | 61 ++++++ seed/php-model/bytes/src/Core/JsonDecoder.php | 160 -------------- .../bytes/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-model/bytes/src/Core/JsonEncoder.php | 20 -- .../php-model/bytes/src/Core/JsonProperty.php | 13 -- .../bytes/src/Core/JsonSerializer.php | 190 ---------------- .../bytes/src/Core/SerializableType.php | 179 --------------- .../bytes/src/Core/Types/ArrayType.php | 16 ++ .../bytes/src/Core/Types/Constant.php | 12 ++ .../bytes/src/Core/Types/DateType.php | 16 ++ seed/php-model/bytes/src/Core/Types/Union.php | 62 ++++++ seed/php-model/bytes/src/Core/Union.php | 62 ------ seed/php-model/bytes/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../bytes/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../bytes/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../bytes/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../bytes/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../bytes/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../circular-references-advanced/src/A/A.php | 2 +- .../src/Ast/ObjectFieldValue.php | 4 +- .../src/Ast/ObjectValue.php | 2 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/ImportingA.php | 4 +- .../src/RootType.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../php-model/circular-references/src/A/A.php | 2 +- .../src/Ast/ObjectValue.php | 2 +- .../src/Core/ArrayType.php | 16 -- .../circular-references/src/Core/Constant.php | 12 -- .../circular-references/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../circular-references/src/Core/Union.php | 62 ------ .../circular-references/src/Core/Utils.php | 61 ------ .../circular-references/src/ImportingA.php | 4 +- .../circular-references/src/RootType.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/FolderA/Service/Response.php | 4 +- .../src/FolderB/Common/Foo.php | 4 +- .../src/FolderC/Common/Foo.php | 4 +- .../src/FolderD/Service/Response.php | 4 +- .../src/Foo/ImportingType.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../custom-auth/src/Core/ArrayType.php | 16 -- .../custom-auth/src/Core/Constant.php | 12 -- .../custom-auth/src/Core/DateType.php | 16 -- .../custom-auth/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../custom-auth/src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../custom-auth/src/Core/Json/Utils.php | 61 ++++++ .../custom-auth/src/Core/JsonDecoder.php | 160 -------------- .../custom-auth/src/Core/JsonDeserializer.php | 202 ----------------- .../custom-auth/src/Core/JsonEncoder.php | 20 -- .../custom-auth/src/Core/JsonProperty.php | 13 -- .../custom-auth/src/Core/JsonSerializer.php | 190 ---------------- .../custom-auth/src/Core/SerializableType.php | 179 --------------- .../custom-auth/src/Core/Types/ArrayType.php | 16 ++ .../custom-auth/src/Core/Types/Constant.php | 12 ++ .../custom-auth/src/Core/Types/DateType.php | 16 ++ .../custom-auth/src/Core/Types/Union.php | 62 ++++++ seed/php-model/custom-auth/src/Core/Union.php | 62 ------ seed/php-model/custom-auth/src/Core/Utils.php | 61 ------ .../Errors/UnauthorizedRequestErrorBody.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../custom-auth/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/enum/src/Core/ArrayType.php | 16 -- seed/php-model/enum/src/Core/Constant.php | 12 -- seed/php-model/enum/src/Core/DateType.php | 16 -- .../enum/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../enum/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../enum/src/Core/Json/JsonEncoder.php | 20 ++ .../enum/src/Core/Json/JsonProperty.php | 13 ++ .../enum/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../enum/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-model/enum/src/Core/Json/Utils.php | 61 ++++++ seed/php-model/enum/src/Core/JsonDecoder.php | 160 -------------- .../enum/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-model/enum/src/Core/JsonEncoder.php | 20 -- seed/php-model/enum/src/Core/JsonProperty.php | 13 -- .../enum/src/Core/JsonSerializer.php | 190 ---------------- .../enum/src/Core/SerializableType.php | 179 --------------- .../enum/src/Core/Types/ArrayType.php | 16 ++ .../enum/src/Core/Types/Constant.php | 12 ++ .../enum/src/Core/Types/DateType.php | 16 ++ seed/php-model/enum/src/Core/Types/Union.php | 62 ++++++ seed/php-model/enum/src/Core/Union.php | 62 ------ seed/php-model/enum/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../enum/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../enum/tests/Seed/Core/EnumTest.php | 76 ------- .../enum/tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../enum/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../enum/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../enum/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../error-property/src/Core/ArrayType.php | 16 -- .../error-property/src/Core/Constant.php | 12 -- .../error-property/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../error-property/src/Core/Json/Utils.php | 61 ++++++ .../error-property/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../error-property/src/Core/JsonEncoder.php | 20 -- .../error-property/src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../error-property/src/Core/Types/Union.php | 62 ++++++ .../error-property/src/Core/Union.php | 62 ------ .../error-property/src/Core/Utils.php | 61 ------ .../src/Errors/PropertyBasedErrorTestBody.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../examples/src/Commons/Types/Metadata.php | 6 +- .../php-model/examples/src/Core/ArrayType.php | 16 -- seed/php-model/examples/src/Core/Constant.php | 12 -- seed/php-model/examples/src/Core/DateType.php | 16 -- .../examples/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../examples/src/Core/Json/JsonEncoder.php | 20 ++ .../examples/src/Core/Json/JsonProperty.php | 13 ++ .../examples/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../examples/src/Core/Json/Utils.php | 61 ++++++ .../examples/src/Core/JsonDecoder.php | 160 -------------- .../examples/src/Core/JsonDeserializer.php | 202 ----------------- .../examples/src/Core/JsonEncoder.php | 20 -- .../examples/src/Core/JsonProperty.php | 13 -- .../examples/src/Core/JsonSerializer.php | 190 ---------------- .../examples/src/Core/SerializableType.php | 179 --------------- .../examples/src/Core/Types/ArrayType.php | 16 ++ .../examples/src/Core/Types/Constant.php | 12 ++ .../examples/src/Core/Types/DateType.php | 16 ++ .../examples/src/Core/Types/Union.php | 62 ++++++ seed/php-model/examples/src/Core/Union.php | 62 ------ seed/php-model/examples/src/Core/Utils.php | 61 ------ seed/php-model/examples/src/Identifier.php | 4 +- seed/php-model/examples/src/Types/Actor.php | 4 +- seed/php-model/examples/src/Types/Actress.php | 4 +- .../examples/src/Types/Directory.php | 6 +- seed/php-model/examples/src/Types/Entity.php | 4 +- .../examples/src/Types/ExceptionInfo.php | 4 +- .../examples/src/Types/ExtendedMovie.php | 6 +- seed/php-model/examples/src/Types/File.php | 4 +- .../examples/src/Types/Migration.php | 4 +- seed/php-model/examples/src/Types/Moment.php | 6 +- seed/php-model/examples/src/Types/Movie.php | 6 +- seed/php-model/examples/src/Types/Node.php | 6 +- seed/php-model/examples/src/Types/Request.php | 4 +- .../php-model/examples/src/Types/Response.php | 6 +- .../examples/src/Types/ResponseType.php | 4 +- .../examples/src/Types/StuntDouble.php | 4 +- seed/php-model/examples/src/Types/Tree.php | 6 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../examples/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../examples/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../exhaustive/src/Core/ArrayType.php | 16 -- .../exhaustive/src/Core/Constant.php | 12 -- .../exhaustive/src/Core/DateType.php | 16 -- .../exhaustive/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../exhaustive/src/Core/Json/JsonEncoder.php | 20 ++ .../exhaustive/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../exhaustive/src/Core/Json/Utils.php | 61 ++++++ .../exhaustive/src/Core/JsonDecoder.php | 160 -------------- .../exhaustive/src/Core/JsonDeserializer.php | 202 ----------------- .../exhaustive/src/Core/JsonEncoder.php | 20 -- .../exhaustive/src/Core/JsonProperty.php | 13 -- .../exhaustive/src/Core/JsonSerializer.php | 190 ---------------- .../exhaustive/src/Core/SerializableType.php | 179 --------------- .../exhaustive/src/Core/Types/ArrayType.php | 16 ++ .../exhaustive/src/Core/Types/Constant.php | 12 ++ .../exhaustive/src/Core/Types/DateType.php | 16 ++ .../exhaustive/src/Core/Types/Union.php | 62 ++++++ seed/php-model/exhaustive/src/Core/Union.php | 62 ------ seed/php-model/exhaustive/src/Core/Utils.php | 61 ------ .../GeneralErrors/BadObjectRequestInfo.php | 4 +- .../src/Types/Object/DoubleOptional.php | 4 +- .../Object/NestedObjectWithOptionalField.php | 4 +- .../Object/NestedObjectWithRequiredField.php | 4 +- .../src/Types/Object/ObjectWithMapOfMap.php | 6 +- .../Types/Object/ObjectWithOptionalField.php | 8 +- .../Types/Object/ObjectWithRequiredField.php | 4 +- .../exhaustive/src/Types/Union/Cat.php | 4 +- .../exhaustive/src/Types/Union/Dog.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../exhaustive/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/extends/src/Core/ArrayType.php | 16 -- seed/php-model/extends/src/Core/Constant.php | 12 -- seed/php-model/extends/src/Core/DateType.php | 16 -- .../extends/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../extends/src/Core/Json/JsonEncoder.php | 20 ++ .../extends/src/Core/Json/JsonProperty.php | 13 ++ .../extends/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-model/extends/src/Core/Json/Utils.php | 61 ++++++ .../extends/src/Core/JsonDecoder.php | 160 -------------- .../extends/src/Core/JsonDeserializer.php | 202 ----------------- .../extends/src/Core/JsonEncoder.php | 20 -- .../extends/src/Core/JsonProperty.php | 13 -- .../extends/src/Core/JsonSerializer.php | 190 ---------------- .../extends/src/Core/SerializableType.php | 179 --------------- .../extends/src/Core/Types/ArrayType.php | 16 ++ .../extends/src/Core/Types/Constant.php | 12 ++ .../extends/src/Core/Types/DateType.php | 16 ++ .../extends/src/Core/Types/Union.php | 62 ++++++ seed/php-model/extends/src/Core/Union.php | 62 ------ seed/php-model/extends/src/Core/Utils.php | 61 ------ seed/php-model/extends/src/Docs.php | 4 +- seed/php-model/extends/src/ExampleType.php | 4 +- seed/php-model/extends/src/Json.php | 4 +- seed/php-model/extends/src/NestedType.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../extends/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../extends/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../extends/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../extra-properties/src/Core/ArrayType.php | 16 -- .../extra-properties/src/Core/Constant.php | 12 -- .../extra-properties/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../extra-properties/src/Core/Json/Utils.php | 61 ++++++ .../extra-properties/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../extra-properties/src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../extra-properties/src/Core/Types/Union.php | 62 ++++++ .../extra-properties/src/Core/Union.php | 62 ------ .../extra-properties/src/Core/Utils.php | 61 ------ .../extra-properties/src/Failure.php | 4 +- .../extra-properties/src/User/User.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../file-download/src/Core/ArrayType.php | 16 -- .../file-download/src/Core/Constant.php | 12 -- .../file-download/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../file-download/src/Core/Json/Utils.php | 61 ++++++ .../file-download/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../file-download/src/Core/JsonEncoder.php | 20 -- .../file-download/src/Core/JsonProperty.php | 13 -- .../file-download/src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../file-download/src/Core/Types/Constant.php | 12 ++ .../file-download/src/Core/Types/DateType.php | 16 ++ .../file-download/src/Core/Types/Union.php | 62 ++++++ .../file-download/src/Core/Union.php | 62 ------ .../file-download/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../file-upload/src/Core/ArrayType.php | 16 -- .../file-upload/src/Core/Constant.php | 12 -- .../file-upload/src/Core/DateType.php | 16 -- .../file-upload/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../file-upload/src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../file-upload/src/Core/Json/Utils.php | 61 ++++++ .../file-upload/src/Core/JsonDecoder.php | 160 -------------- .../file-upload/src/Core/JsonDeserializer.php | 202 ----------------- .../file-upload/src/Core/JsonEncoder.php | 20 -- .../file-upload/src/Core/JsonProperty.php | 13 -- .../file-upload/src/Core/JsonSerializer.php | 190 ---------------- .../file-upload/src/Core/SerializableType.php | 179 --------------- .../file-upload/src/Core/Types/ArrayType.php | 16 ++ .../file-upload/src/Core/Types/Constant.php | 12 ++ .../file-upload/src/Core/Types/DateType.php | 16 ++ .../file-upload/src/Core/Types/Union.php | 62 ++++++ seed/php-model/file-upload/src/Core/Union.php | 62 ------ seed/php-model/file-upload/src/Core/Utils.php | 61 ------ .../file-upload/src/Service/MyObject.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../file-upload/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/folders/src/Core/ArrayType.php | 16 -- seed/php-model/folders/src/Core/Constant.php | 12 -- seed/php-model/folders/src/Core/DateType.php | 16 -- .../folders/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../folders/src/Core/Json/JsonEncoder.php | 20 ++ .../folders/src/Core/Json/JsonProperty.php | 13 ++ .../folders/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-model/folders/src/Core/Json/Utils.php | 61 ++++++ .../folders/src/Core/JsonDecoder.php | 160 -------------- .../folders/src/Core/JsonDeserializer.php | 202 ----------------- .../folders/src/Core/JsonEncoder.php | 20 -- .../folders/src/Core/JsonProperty.php | 13 -- .../folders/src/Core/JsonSerializer.php | 190 ---------------- .../folders/src/Core/SerializableType.php | 179 --------------- .../folders/src/Core/Types/ArrayType.php | 16 ++ .../folders/src/Core/Types/Constant.php | 12 ++ .../folders/src/Core/Types/DateType.php | 16 ++ .../folders/src/Core/Types/Union.php | 62 ++++++ seed/php-model/folders/src/Core/Union.php | 62 ------ seed/php-model/folders/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../folders/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../folders/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../folders/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../grpc-proto-exhaustive/src/Column.php | 8 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../grpc-proto-exhaustive/src/Core/Union.php | 62 ------ .../grpc-proto-exhaustive/src/Core/Utils.php | 61 ------ .../src/DeleteResponse.php | 2 +- .../src/DescribeResponse.php | 6 +- .../src/FetchResponse.php | 6 +- .../grpc-proto-exhaustive/src/IndexedData.php | 6 +- .../grpc-proto-exhaustive/src/ListElement.php | 4 +- .../src/ListResponse.php | 6 +- .../src/NamespaceSummary.php | 4 +- .../grpc-proto-exhaustive/src/Pagination.php | 4 +- .../grpc-proto-exhaustive/src/QueryColumn.php | 8 +- .../src/QueryResponse.php | 6 +- .../grpc-proto-exhaustive/src/QueryResult.php | 6 +- .../src/ScoredColumn.php | 8 +- .../src/UpdateResponse.php | 2 +- .../src/UploadResponse.php | 4 +- .../grpc-proto-exhaustive/src/Usage.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../grpc-proto/src/Core/ArrayType.php | 16 -- .../grpc-proto/src/Core/Constant.php | 12 -- .../grpc-proto/src/Core/DateType.php | 16 -- .../grpc-proto/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../grpc-proto/src/Core/Json/JsonEncoder.php | 20 ++ .../grpc-proto/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../grpc-proto/src/Core/Json/Utils.php | 61 ++++++ .../grpc-proto/src/Core/JsonDecoder.php | 160 -------------- .../grpc-proto/src/Core/JsonDeserializer.php | 202 ----------------- .../grpc-proto/src/Core/JsonEncoder.php | 20 -- .../grpc-proto/src/Core/JsonProperty.php | 13 -- .../grpc-proto/src/Core/JsonSerializer.php | 190 ---------------- .../grpc-proto/src/Core/SerializableType.php | 179 --------------- .../grpc-proto/src/Core/Types/ArrayType.php | 16 ++ .../grpc-proto/src/Core/Types/Constant.php | 12 ++ .../grpc-proto/src/Core/Types/DateType.php | 16 ++ .../grpc-proto/src/Core/Types/Union.php | 62 ++++++ seed/php-model/grpc-proto/src/Core/Union.php | 62 ------ seed/php-model/grpc-proto/src/Core/Utils.php | 61 ------ .../grpc-proto/src/CreateResponse.php | 4 +- seed/php-model/grpc-proto/src/UserModel.php | 6 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../grpc-proto/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../idempotency-headers/src/Core/Constant.php | 12 -- .../idempotency-headers/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../idempotency-headers/src/Core/Union.php | 62 ------ .../idempotency-headers/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/imdb/src/Core/ArrayType.php | 16 -- seed/php-model/imdb/src/Core/Constant.php | 12 -- seed/php-model/imdb/src/Core/DateType.php | 16 -- .../imdb/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../imdb/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../imdb/src/Core/Json/JsonEncoder.php | 20 ++ .../imdb/src/Core/Json/JsonProperty.php | 13 ++ .../imdb/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../imdb/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-model/imdb/src/Core/Json/Utils.php | 61 ++++++ seed/php-model/imdb/src/Core/JsonDecoder.php | 160 -------------- .../imdb/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-model/imdb/src/Core/JsonEncoder.php | 20 -- seed/php-model/imdb/src/Core/JsonProperty.php | 13 -- .../imdb/src/Core/JsonSerializer.php | 190 ---------------- .../imdb/src/Core/SerializableType.php | 179 --------------- .../imdb/src/Core/Types/ArrayType.php | 16 ++ .../imdb/src/Core/Types/Constant.php | 12 ++ .../imdb/src/Core/Types/DateType.php | 16 ++ seed/php-model/imdb/src/Core/Types/Union.php | 62 ++++++ seed/php-model/imdb/src/Core/Union.php | 62 ------ seed/php-model/imdb/src/Core/Utils.php | 61 ------ .../imdb/src/Imdb/CreateMovieRequest.php | 4 +- seed/php-model/imdb/src/Imdb/Movie.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../imdb/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../imdb/tests/Seed/Core/EnumTest.php | 76 ------- .../imdb/tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../imdb/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../imdb/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../imdb/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/literal/src/Core/ArrayType.php | 16 -- seed/php-model/literal/src/Core/Constant.php | 12 -- seed/php-model/literal/src/Core/DateType.php | 16 -- .../literal/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../literal/src/Core/Json/JsonEncoder.php | 20 ++ .../literal/src/Core/Json/JsonProperty.php | 13 ++ .../literal/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-model/literal/src/Core/Json/Utils.php | 61 ++++++ .../literal/src/Core/JsonDecoder.php | 160 -------------- .../literal/src/Core/JsonDeserializer.php | 202 ----------------- .../literal/src/Core/JsonEncoder.php | 20 -- .../literal/src/Core/JsonProperty.php | 13 -- .../literal/src/Core/JsonSerializer.php | 190 ---------------- .../literal/src/Core/SerializableType.php | 179 --------------- .../literal/src/Core/Types/ArrayType.php | 16 ++ .../literal/src/Core/Types/Constant.php | 12 ++ .../literal/src/Core/Types/DateType.php | 16 ++ .../literal/src/Core/Types/Union.php | 62 ++++++ seed/php-model/literal/src/Core/Union.php | 62 ------ seed/php-model/literal/src/Core/Utils.php | 61 ------ .../literal/src/Inlined/ANestedLiteral.php | 4 +- .../literal/src/Inlined/ATopLevelLiteral.php | 4 +- .../literal/src/Reference/ContainerObject.php | 6 +- .../Reference/NestedObjectWithLiterals.php | 4 +- .../literal/src/Reference/SendRequest.php | 4 +- seed/php-model/literal/src/SendResponse.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../literal/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../literal/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../literal/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../mixed-case/src/Core/ArrayType.php | 16 -- .../mixed-case/src/Core/Constant.php | 12 -- .../mixed-case/src/Core/DateType.php | 16 -- .../mixed-case/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../mixed-case/src/Core/Json/JsonEncoder.php | 20 ++ .../mixed-case/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../mixed-case/src/Core/Json/Utils.php | 61 ++++++ .../mixed-case/src/Core/JsonDecoder.php | 160 -------------- .../mixed-case/src/Core/JsonDeserializer.php | 202 ----------------- .../mixed-case/src/Core/JsonEncoder.php | 20 -- .../mixed-case/src/Core/JsonProperty.php | 13 -- .../mixed-case/src/Core/JsonSerializer.php | 190 ---------------- .../mixed-case/src/Core/SerializableType.php | 179 --------------- .../mixed-case/src/Core/Types/ArrayType.php | 16 ++ .../mixed-case/src/Core/Types/Constant.php | 12 ++ .../mixed-case/src/Core/Types/DateType.php | 16 ++ .../mixed-case/src/Core/Types/Union.php | 62 ++++++ seed/php-model/mixed-case/src/Core/Union.php | 62 ------ seed/php-model/mixed-case/src/Core/Utils.php | 61 ------ .../mixed-case/src/Service/NestedUser.php | 4 +- .../mixed-case/src/Service/Organization.php | 4 +- .../php-model/mixed-case/src/Service/User.php | 6 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../mixed-case/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../mixed-file-directory/src/Core/Union.php | 62 ------ .../mixed-file-directory/src/Core/Utils.php | 61 ------ .../CreateOrganizationRequest.php | 4 +- .../src/Organization/Organization.php | 6 +- .../src/User/Events/Event.php | 4 +- .../src/User/Events/Metadata/Metadata.php | 4 +- .../mixed-file-directory/src/User/User.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../multi-line-docs/src/Core/ArrayType.php | 16 -- .../multi-line-docs/src/Core/Constant.php | 12 -- .../multi-line-docs/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../multi-line-docs/src/Core/Json/Utils.php | 61 ++++++ .../multi-line-docs/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../multi-line-docs/src/Core/JsonEncoder.php | 20 -- .../multi-line-docs/src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../multi-line-docs/src/Core/Types/Union.php | 62 ++++++ .../multi-line-docs/src/Core/Union.php | 62 ------ .../multi-line-docs/src/Core/Utils.php | 61 ------ .../multi-line-docs/src/User/User.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../multi-url-environment/src/Core/Union.php | 62 ------ .../multi-url-environment/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../no-environment/src/Core/ArrayType.php | 16 -- .../no-environment/src/Core/Constant.php | 12 -- .../no-environment/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../no-environment/src/Core/Json/Utils.php | 61 ++++++ .../no-environment/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../no-environment/src/Core/JsonEncoder.php | 20 -- .../no-environment/src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../no-environment/src/Core/Types/Union.php | 62 ++++++ .../no-environment/src/Core/Union.php | 62 ------ .../no-environment/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Auth/TokenResponse.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Auth/TokenResponse.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Auth/TokenResponse.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Auth/TokenResponse.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/object/src/Core/ArrayType.php | 16 -- seed/php-model/object/src/Core/Constant.php | 12 -- seed/php-model/object/src/Core/DateType.php | 16 -- .../object/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../object/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../object/src/Core/Json/JsonEncoder.php | 20 ++ .../object/src/Core/Json/JsonProperty.php | 13 ++ .../object/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../object/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-model/object/src/Core/Json/Utils.php | 61 ++++++ .../php-model/object/src/Core/JsonDecoder.php | 160 -------------- .../object/src/Core/JsonDeserializer.php | 202 ----------------- .../php-model/object/src/Core/JsonEncoder.php | 20 -- .../object/src/Core/JsonProperty.php | 13 -- .../object/src/Core/JsonSerializer.php | 190 ---------------- .../object/src/Core/SerializableType.php | 179 --------------- .../object/src/Core/Types/ArrayType.php | 16 ++ .../object/src/Core/Types/Constant.php | 12 ++ .../object/src/Core/Types/DateType.php | 16 ++ .../php-model/object/src/Core/Types/Union.php | 62 ++++++ seed/php-model/object/src/Core/Union.php | 62 ------ seed/php-model/object/src/Core/Utils.php | 61 ------ seed/php-model/object/src/Name.php | 4 +- seed/php-model/object/src/Type.php | 10 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../object/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../object/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../object/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Commons/Metadata/Metadata.php | 6 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../objects-with-imports/src/Core/Union.php | 62 ------ .../objects-with-imports/src/Core/Utils.php | 61 ------ .../src/File/Directory/Directory.php | 6 +- .../objects-with-imports/src/File/File.php | 4 +- .../objects-with-imports/src/Node.php | 4 +- .../objects-with-imports/src/Tree.php | 6 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../php-model/optional/src/Core/ArrayType.php | 16 -- seed/php-model/optional/src/Core/Constant.php | 12 -- seed/php-model/optional/src/Core/DateType.php | 16 -- .../optional/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../optional/src/Core/Json/JsonEncoder.php | 20 ++ .../optional/src/Core/Json/JsonProperty.php | 13 ++ .../optional/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../optional/src/Core/Json/Utils.php | 61 ++++++ .../optional/src/Core/JsonDecoder.php | 160 -------------- .../optional/src/Core/JsonDeserializer.php | 202 ----------------- .../optional/src/Core/JsonEncoder.php | 20 -- .../optional/src/Core/JsonProperty.php | 13 -- .../optional/src/Core/JsonSerializer.php | 190 ---------------- .../optional/src/Core/SerializableType.php | 179 --------------- .../optional/src/Core/Types/ArrayType.php | 16 ++ .../optional/src/Core/Types/Constant.php | 12 ++ .../optional/src/Core/Types/DateType.php | 16 ++ .../optional/src/Core/Types/Union.php | 62 ++++++ seed/php-model/optional/src/Core/Union.php | 62 ------ seed/php-model/optional/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../optional/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../optional/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../package-yml/src/Core/ArrayType.php | 16 -- .../package-yml/src/Core/Constant.php | 12 -- .../package-yml/src/Core/DateType.php | 16 -- .../package-yml/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../package-yml/src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../package-yml/src/Core/Json/Utils.php | 61 ++++++ .../package-yml/src/Core/JsonDecoder.php | 160 -------------- .../package-yml/src/Core/JsonDeserializer.php | 202 ----------------- .../package-yml/src/Core/JsonEncoder.php | 20 -- .../package-yml/src/Core/JsonProperty.php | 13 -- .../package-yml/src/Core/JsonSerializer.php | 190 ---------------- .../package-yml/src/Core/SerializableType.php | 179 --------------- .../package-yml/src/Core/Types/ArrayType.php | 16 ++ .../package-yml/src/Core/Types/Constant.php | 12 ++ .../package-yml/src/Core/Types/DateType.php | 16 ++ .../package-yml/src/Core/Types/Union.php | 62 ++++++ seed/php-model/package-yml/src/Core/Union.php | 62 ------ seed/php-model/package-yml/src/Core/Utils.php | 61 ------ .../php-model/package-yml/src/EchoRequest.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../package-yml/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../pagination/src/Core/ArrayType.php | 16 -- .../pagination/src/Core/Constant.php | 12 -- .../pagination/src/Core/DateType.php | 16 -- .../pagination/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../pagination/src/Core/Json/JsonEncoder.php | 20 ++ .../pagination/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../pagination/src/Core/Json/Utils.php | 61 ++++++ .../pagination/src/Core/JsonDecoder.php | 160 -------------- .../pagination/src/Core/JsonDeserializer.php | 202 ----------------- .../pagination/src/Core/JsonEncoder.php | 20 -- .../pagination/src/Core/JsonProperty.php | 13 -- .../pagination/src/Core/JsonSerializer.php | 190 ---------------- .../pagination/src/Core/SerializableType.php | 179 --------------- .../pagination/src/Core/Types/ArrayType.php | 16 ++ .../pagination/src/Core/Types/Constant.php | 12 ++ .../pagination/src/Core/Types/DateType.php | 16 ++ .../pagination/src/Core/Types/Union.php | 62 ++++++ seed/php-model/pagination/src/Core/Union.php | 62 ------ seed/php-model/pagination/src/Core/Utils.php | 61 ------ .../pagination/src/UsernameCursor.php | 4 +- .../php-model/pagination/src/UsernamePage.php | 6 +- .../ListUsersExtendedOptionalListResponse.php | 4 +- .../src/Users/ListUsersExtendedResponse.php | 4 +- .../src/Users/ListUsersPaginationResponse.php | 6 +- .../pagination/src/Users/NextPage.php | 4 +- seed/php-model/pagination/src/Users/Page.php | 4 +- seed/php-model/pagination/src/Users/User.php | 4 +- .../src/Users/UserListContainer.php | 6 +- .../src/Users/UserOptionalListContainer.php | 6 +- .../src/Users/UserOptionalListPage.php | 4 +- .../pagination/src/Users/UserPage.php | 4 +- .../src/Users/UsernameContainer.php | 6 +- .../pagination/src/Users/WithCursor.php | 4 +- .../pagination/src/Users/WithPage.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../pagination/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../plain-text/src/Core/ArrayType.php | 16 -- .../plain-text/src/Core/Constant.php | 12 -- .../plain-text/src/Core/DateType.php | 16 -- .../plain-text/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../plain-text/src/Core/Json/JsonEncoder.php | 20 ++ .../plain-text/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../plain-text/src/Core/Json/Utils.php | 61 ++++++ .../plain-text/src/Core/JsonDecoder.php | 160 -------------- .../plain-text/src/Core/JsonDeserializer.php | 202 ----------------- .../plain-text/src/Core/JsonEncoder.php | 20 -- .../plain-text/src/Core/JsonProperty.php | 13 -- .../plain-text/src/Core/JsonSerializer.php | 190 ---------------- .../plain-text/src/Core/SerializableType.php | 179 --------------- .../plain-text/src/Core/Types/ArrayType.php | 16 ++ .../plain-text/src/Core/Types/Constant.php | 12 ++ .../plain-text/src/Core/Types/DateType.php | 16 ++ .../plain-text/src/Core/Types/Union.php | 62 ++++++ seed/php-model/plain-text/src/Core/Union.php | 62 ------ seed/php-model/plain-text/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../plain-text/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../query-parameters/src/Core/ArrayType.php | 16 -- .../query-parameters/src/Core/Constant.php | 12 -- .../query-parameters/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../query-parameters/src/Core/Json/Utils.php | 61 ++++++ .../query-parameters/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../query-parameters/src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../query-parameters/src/Core/Types/Union.php | 62 ++++++ .../query-parameters/src/Core/Union.php | 62 ------ .../query-parameters/src/Core/Utils.php | 61 ------ .../query-parameters/src/User/NestedUser.php | 4 +- .../query-parameters/src/User/User.php | 6 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../reserved-keywords/src/Core/ArrayType.php | 16 -- .../reserved-keywords/src/Core/Constant.php | 12 -- .../reserved-keywords/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../reserved-keywords/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../reserved-keywords/src/Core/Union.php | 62 ------ .../reserved-keywords/src/Core/Utils.php | 61 ------ .../reserved-keywords/src/Package/Package.php | 4 +- .../reserved-keywords/src/Package/Record.php | 6 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../response-property/src/Core/ArrayType.php | 16 -- .../response-property/src/Core/Constant.php | 12 -- .../response-property/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../response-property/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../response-property/src/Core/Union.php | 62 ------ .../response-property/src/Core/Utils.php | 61 ------ .../response-property/src/Service/Movie.php | 4 +- .../src/Service/Response.php | 4 +- .../src/Service/WithDocs.php | 4 +- .../response-property/src/StringResponse.php | 4 +- .../response-property/src/WithMetadata.php | 6 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Completions/StreamedCompletion.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Completions/StreamedCompletion.php | 4 +- .../server-sent-events/src/Core/ArrayType.php | 16 -- .../server-sent-events/src/Core/Constant.php | 12 -- .../server-sent-events/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../server-sent-events/src/Core/Union.php | 62 ------ .../server-sent-events/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/simple-fhir/src/Account.php | 4 +- .../simple-fhir/src/BaseResource.php | 8 +- .../simple-fhir/src/Core/ArrayType.php | 16 -- .../simple-fhir/src/Core/Constant.php | 12 -- .../simple-fhir/src/Core/DateType.php | 16 -- .../simple-fhir/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../simple-fhir/src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../simple-fhir/src/Core/Json/Utils.php | 61 ++++++ .../simple-fhir/src/Core/JsonDecoder.php | 160 -------------- .../simple-fhir/src/Core/JsonDeserializer.php | 202 ----------------- .../simple-fhir/src/Core/JsonEncoder.php | 20 -- .../simple-fhir/src/Core/JsonProperty.php | 13 -- .../simple-fhir/src/Core/JsonSerializer.php | 190 ---------------- .../simple-fhir/src/Core/SerializableType.php | 179 --------------- .../simple-fhir/src/Core/Types/ArrayType.php | 16 ++ .../simple-fhir/src/Core/Types/Constant.php | 12 ++ .../simple-fhir/src/Core/Types/DateType.php | 16 ++ .../simple-fhir/src/Core/Types/Union.php | 62 ++++++ seed/php-model/simple-fhir/src/Core/Union.php | 62 ------ seed/php-model/simple-fhir/src/Core/Utils.php | 61 ------ seed/php-model/simple-fhir/src/Memo.php | 4 +- seed/php-model/simple-fhir/src/Patient.php | 6 +- .../simple-fhir/src/Practitioner.php | 4 +- seed/php-model/simple-fhir/src/Script.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../simple-fhir/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../streaming-parameter/src/Core/Constant.php | 12 -- .../streaming-parameter/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../streaming-parameter/src/Core/Union.php | 62 ------ .../streaming-parameter/src/Core/Utils.php | 61 ------ .../src/Dummy/RegularResponse.php | 4 +- .../src/Dummy/StreamResponse.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../streaming/src/Core/ArrayType.php | 16 -- .../php-model/streaming/src/Core/Constant.php | 12 -- .../php-model/streaming/src/Core/DateType.php | 16 -- .../streaming/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../streaming/src/Core/Json/JsonEncoder.php | 20 ++ .../streaming/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../streaming/src/Core/Json/Utils.php | 61 ++++++ .../streaming/src/Core/JsonDecoder.php | 160 -------------- .../streaming/src/Core/JsonDeserializer.php | 202 ----------------- .../streaming/src/Core/JsonEncoder.php | 20 -- .../streaming/src/Core/JsonProperty.php | 13 -- .../streaming/src/Core/JsonSerializer.php | 190 ---------------- .../streaming/src/Core/SerializableType.php | 179 --------------- .../streaming/src/Core/Types/ArrayType.php | 16 ++ .../streaming/src/Core/Types/Constant.php | 12 ++ .../streaming/src/Core/Types/DateType.php | 16 ++ .../streaming/src/Core/Types/Union.php | 62 ++++++ seed/php-model/streaming/src/Core/Union.php | 62 ------ seed/php-model/streaming/src/Core/Utils.php | 61 ------ .../streaming/src/Dummy/StreamResponse.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../streaming/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../Commons/BinaryTreeNodeAndTreeValue.php | 4 +- .../trace/src/Commons/BinaryTreeNodeValue.php | 4 +- .../trace/src/Commons/BinaryTreeValue.php | 6 +- .../trace/src/Commons/DebugKeyValuePairs.php | 4 +- .../trace/src/Commons/DebugMapValue.php | 6 +- .../DoublyLinkedListNodeAndListValue.php | 4 +- .../src/Commons/DoublyLinkedListNodeValue.php | 4 +- .../src/Commons/DoublyLinkedListValue.php | 6 +- seed/php-model/trace/src/Commons/FileInfo.php | 4 +- .../trace/src/Commons/GenericValue.php | 4 +- .../trace/src/Commons/KeyValuePair.php | 4 +- seed/php-model/trace/src/Commons/ListType.php | 4 +- seed/php-model/trace/src/Commons/MapType.php | 4 +- seed/php-model/trace/src/Commons/MapValue.php | 6 +- .../SinglyLinkedListNodeAndListValue.php | 4 +- .../src/Commons/SinglyLinkedListNodeValue.php | 4 +- .../src/Commons/SinglyLinkedListValue.php | 6 +- seed/php-model/trace/src/Commons/TestCase.php | 6 +- .../Commons/TestCaseWithExpectedResult.php | 4 +- seed/php-model/trace/src/Core/ArrayType.php | 16 -- seed/php-model/trace/src/Core/Constant.php | 12 -- seed/php-model/trace/src/Core/DateType.php | 16 -- .../trace/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../trace/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../trace/src/Core/Json/JsonEncoder.php | 20 ++ .../trace/src/Core/Json/JsonProperty.php | 13 ++ .../trace/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../trace/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-model/trace/src/Core/Json/Utils.php | 61 ++++++ seed/php-model/trace/src/Core/JsonDecoder.php | 160 -------------- .../trace/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-model/trace/src/Core/JsonEncoder.php | 20 -- .../php-model/trace/src/Core/JsonProperty.php | 13 -- .../trace/src/Core/JsonSerializer.php | 190 ---------------- .../trace/src/Core/SerializableType.php | 179 --------------- .../trace/src/Core/Types/ArrayType.php | 16 ++ .../trace/src/Core/Types/Constant.php | 12 ++ .../trace/src/Core/Types/DateType.php | 16 ++ seed/php-model/trace/src/Core/Types/Union.php | 62 ++++++ seed/php-model/trace/src/Core/Union.php | 62 ------ seed/php-model/trace/src/Core/Utils.php | 61 ------ .../src/LangServer/LangServerRequest.php | 4 +- .../src/LangServer/LangServerResponse.php | 4 +- .../trace/src/Migration/Migration.php | 4 +- .../php-model/trace/src/Playlist/Playlist.php | 4 +- .../src/Playlist/PlaylistCreateRequest.php | 6 +- .../src/Playlist/UpdatePlaylistRequest.php | 6 +- .../src/Problem/CreateProblemRequest.php | 6 +- .../src/Problem/GenericCreateProblemError.php | 4 +- .../GetDefaultStarterFilesResponse.php | 6 +- .../trace/src/Problem/ProblemDescription.php | 6 +- .../trace/src/Problem/ProblemFiles.php | 6 +- .../trace/src/Problem/ProblemInfo.php | 6 +- .../src/Problem/UpdateProblemResponse.php | 4 +- .../trace/src/Problem/VariableTypeAndName.php | 4 +- .../Submission/BuildingExecutorResponse.php | 4 +- .../trace/src/Submission/CompileError.php | 4 +- .../Submission/CustomTestCasesUnsupported.php | 4 +- .../trace/src/Submission/ErroredResponse.php | 4 +- .../trace/src/Submission/ExceptionInfo.php | 4 +- .../Submission/ExecutionSessionResponse.php | 4 +- .../src/Submission/ExecutionSessionState.php | 4 +- .../ExistingSubmissionExecuting.php | 4 +- .../src/Submission/ExpressionLocation.php | 4 +- .../trace/src/Submission/FinishedResponse.php | 4 +- .../GetExecutionSessionStateResponse.php | 6 +- .../Submission/GetSubmissionStateResponse.php | 6 +- .../GetTraceResponsesPageRequest.php | 4 +- .../trace/src/Submission/GradedResponse.php | 6 +- .../trace/src/Submission/GradedResponseV2.php | 6 +- .../src/Submission/GradedTestCaseUpdate.php | 4 +- .../Submission/InitializeProblemRequest.php | 4 +- .../trace/src/Submission/InternalError.php | 4 +- .../src/Submission/InvalidRequestResponse.php | 4 +- .../LightweightStackframeInformation.php | 4 +- .../RecordedResponseNotification.php | 4 +- .../src/Submission/RecordedTestCaseUpdate.php | 4 +- .../RecordingResponseNotification.php | 4 +- .../trace/src/Submission/RunningResponse.php | 4 +- .../trace/src/Submission/RuntimeError.php | 4 +- seed/php-model/trace/src/Submission/Scope.php | 6 +- .../trace/src/Submission/StackFrame.php | 6 +- .../trace/src/Submission/StackInformation.php | 4 +- .../trace/src/Submission/StderrResponse.php | 4 +- .../trace/src/Submission/StdoutResponse.php | 4 +- .../trace/src/Submission/StopRequest.php | 4 +- .../trace/src/Submission/StoppedResponse.php | 4 +- .../src/Submission/SubmissionFileInfo.php | 4 +- .../src/Submission/SubmissionIdNotFound.php | 4 +- .../trace/src/Submission/SubmitRequestV2.php | 6 +- .../src/Submission/TerminatedResponse.php | 2 +- .../src/Submission/TestCaseHiddenGrade.php | 4 +- .../src/Submission/TestCaseNonHiddenGrade.php | 4 +- .../trace/src/Submission/TestCaseResult.php | 4 +- .../Submission/TestCaseResultWithStdout.php | 4 +- .../src/Submission/TestSubmissionState.php | 6 +- .../src/Submission/TestSubmissionStatusV2.php | 6 +- .../src/Submission/TestSubmissionUpdate.php | 6 +- .../trace/src/Submission/TraceResponse.php | 4 +- .../trace/src/Submission/TraceResponseV2.php | 4 +- .../src/Submission/TraceResponsesPage.php | 6 +- .../src/Submission/TraceResponsesPageV2.php | 6 +- .../trace/src/Submission/TracedFile.php | 4 +- .../trace/src/Submission/TracedTestCase.php | 4 +- .../Submission/UnexpectedLanguageError.php | 4 +- .../trace/src/Submission/WorkspaceFiles.php | 6 +- .../src/Submission/WorkspaceRanResponse.php | 4 +- .../src/Submission/WorkspaceRunDetails.php | 4 +- .../WorkspaceStarterFilesResponse.php | 6 +- .../WorkspaceStarterFilesResponseV2.php | 6 +- .../Submission/WorkspaceSubmissionState.php | 4 +- .../WorkspaceSubmissionStatusV2.php | 6 +- .../Submission/WorkspaceSubmissionUpdate.php | 6 +- .../src/Submission/WorkspaceSubmitRequest.php | 6 +- .../src/Submission/WorkspaceTracedUpdate.php | 4 +- .../trace/src/V2/Problem/BasicCustomFiles.php | 6 +- .../src/V2/Problem/BasicTestCaseTemplate.php | 4 +- .../src/V2/Problem/CreateProblemRequestV2.php | 6 +- .../Problem/DeepEqualityCorrectnessCheck.php | 4 +- .../src/V2/Problem/DefaultProvidedFile.php | 6 +- .../trace/src/V2/Problem/FileInfoV2.php | 4 +- seed/php-model/trace/src/V2/Problem/Files.php | 6 +- .../src/V2/Problem/FunctionImplementation.php | 4 +- ...tionImplementationForMultipleLanguages.php | 6 +- .../trace/src/V2/Problem/GeneratedFiles.php | 6 +- .../Problem/GetBasicSolutionFileRequest.php | 4 +- .../Problem/GetBasicSolutionFileResponse.php | 6 +- .../Problem/GetFunctionSignatureRequest.php | 4 +- .../Problem/GetFunctionSignatureResponse.php | 6 +- .../GetGeneratedTestCaseFileRequest.php | 4 +- ...etGeneratedTestCaseTemplateFileRequest.php | 4 +- .../V2/Problem/LightweightProblemInfoV2.php | 6 +- .../V2/Problem/NonVoidFunctionDefinition.php | 4 +- .../V2/Problem/NonVoidFunctionSignature.php | 6 +- .../trace/src/V2/Problem/Parameter.php | 4 +- .../trace/src/V2/Problem/ProblemInfoV2.php | 6 +- .../trace/src/V2/Problem/TestCaseExpects.php | 4 +- .../src/V2/Problem/TestCaseImplementation.php | 4 +- .../TestCaseImplementationDescription.php | 6 +- .../trace/src/V2/Problem/TestCaseMetadata.php | 4 +- .../trace/src/V2/Problem/TestCaseTemplate.php | 4 +- .../trace/src/V2/Problem/TestCaseV2.php | 6 +- ...TestCaseWithActualResultImplementation.php | 4 +- .../src/V2/Problem/VoidFunctionDefinition.php | 6 +- ...unctionDefinitionThatTakesActualResult.php | 6 +- .../src/V2/Problem/VoidFunctionSignature.php | 6 +- ...FunctionSignatureThatTakesActualResult.php | 6 +- .../src/V2/V3/Problem/BasicCustomFiles.php | 6 +- .../V2/V3/Problem/BasicTestCaseTemplate.php | 4 +- .../V2/V3/Problem/CreateProblemRequestV2.php | 6 +- .../Problem/DeepEqualityCorrectnessCheck.php | 4 +- .../src/V2/V3/Problem/DefaultProvidedFile.php | 6 +- .../trace/src/V2/V3/Problem/FileInfoV2.php | 4 +- .../trace/src/V2/V3/Problem/Files.php | 6 +- .../V2/V3/Problem/FunctionImplementation.php | 4 +- ...tionImplementationForMultipleLanguages.php | 6 +- .../src/V2/V3/Problem/GeneratedFiles.php | 6 +- .../Problem/GetBasicSolutionFileRequest.php | 4 +- .../Problem/GetBasicSolutionFileResponse.php | 6 +- .../Problem/GetFunctionSignatureRequest.php | 4 +- .../Problem/GetFunctionSignatureResponse.php | 6 +- .../GetGeneratedTestCaseFileRequest.php | 4 +- ...etGeneratedTestCaseTemplateFileRequest.php | 4 +- .../V3/Problem/LightweightProblemInfoV2.php | 6 +- .../V3/Problem/NonVoidFunctionDefinition.php | 4 +- .../V3/Problem/NonVoidFunctionSignature.php | 6 +- .../trace/src/V2/V3/Problem/Parameter.php | 4 +- .../trace/src/V2/V3/Problem/ProblemInfoV2.php | 6 +- .../src/V2/V3/Problem/TestCaseExpects.php | 4 +- .../V2/V3/Problem/TestCaseImplementation.php | 4 +- .../TestCaseImplementationDescription.php | 6 +- .../src/V2/V3/Problem/TestCaseMetadata.php | 4 +- .../src/V2/V3/Problem/TestCaseTemplate.php | 4 +- .../trace/src/V2/V3/Problem/TestCaseV2.php | 6 +- ...TestCaseWithActualResultImplementation.php | 4 +- .../V2/V3/Problem/VoidFunctionDefinition.php | 6 +- ...unctionDefinitionThatTakesActualResult.php | 6 +- .../V2/V3/Problem/VoidFunctionSignature.php | 6 +- ...FunctionSignatureThatTakesActualResult.php | 6 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../trace/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../trace/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../trace/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../trace/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../trace/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../undiscriminated-unions/src/Core/Union.php | 62 ------ .../undiscriminated-unions/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/unions/src/Core/ArrayType.php | 16 -- seed/php-model/unions/src/Core/Constant.php | 12 -- seed/php-model/unions/src/Core/DateType.php | 16 -- .../unions/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../unions/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../unions/src/Core/Json/JsonEncoder.php | 20 ++ .../unions/src/Core/Json/JsonProperty.php | 13 ++ .../unions/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../unions/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-model/unions/src/Core/Json/Utils.php | 61 ++++++ .../php-model/unions/src/Core/JsonDecoder.php | 160 -------------- .../unions/src/Core/JsonDeserializer.php | 202 ----------------- .../php-model/unions/src/Core/JsonEncoder.php | 20 -- .../unions/src/Core/JsonProperty.php | 13 -- .../unions/src/Core/JsonSerializer.php | 190 ---------------- .../unions/src/Core/SerializableType.php | 179 --------------- .../unions/src/Core/Types/ArrayType.php | 16 ++ .../unions/src/Core/Types/Constant.php | 12 ++ .../unions/src/Core/Types/DateType.php | 16 ++ .../php-model/unions/src/Core/Types/Union.php | 62 ++++++ seed/php-model/unions/src/Core/Union.php | 62 ------ seed/php-model/unions/src/Core/Utils.php | 61 ------ seed/php-model/unions/src/Types/Bar.php | 4 +- seed/php-model/unions/src/Types/Foo.php | 4 +- seed/php-model/unions/src/Union/Circle.php | 4 +- .../unions/src/Union/GetShapeRequest.php | 4 +- seed/php-model/unions/src/Union/Square.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../unions/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../unions/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../unions/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/unknown/src/Core/ArrayType.php | 16 -- seed/php-model/unknown/src/Core/Constant.php | 12 -- seed/php-model/unknown/src/Core/DateType.php | 16 -- .../unknown/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../unknown/src/Core/Json/JsonEncoder.php | 20 ++ .../unknown/src/Core/Json/JsonProperty.php | 13 ++ .../unknown/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-model/unknown/src/Core/Json/Utils.php | 61 ++++++ .../unknown/src/Core/JsonDecoder.php | 160 -------------- .../unknown/src/Core/JsonDeserializer.php | 202 ----------------- .../unknown/src/Core/JsonEncoder.php | 20 -- .../unknown/src/Core/JsonProperty.php | 13 -- .../unknown/src/Core/JsonSerializer.php | 190 ---------------- .../unknown/src/Core/SerializableType.php | 179 --------------- .../unknown/src/Core/Types/ArrayType.php | 16 ++ .../unknown/src/Core/Types/Constant.php | 12 ++ .../unknown/src/Core/Types/DateType.php | 16 ++ .../unknown/src/Core/Types/Union.php | 62 ++++++ seed/php-model/unknown/src/Core/Union.php | 62 ------ seed/php-model/unknown/src/Core/Utils.php | 61 ------ .../unknown/src/Unknown/MyObject.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../unknown/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../unknown/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../unknown/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../validation/src/Core/ArrayType.php | 16 -- .../validation/src/Core/Constant.php | 12 -- .../validation/src/Core/DateType.php | 16 -- .../validation/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../validation/src/Core/Json/JsonEncoder.php | 20 ++ .../validation/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../validation/src/Core/Json/Utils.php | 61 ++++++ .../validation/src/Core/JsonDecoder.php | 160 -------------- .../validation/src/Core/JsonDeserializer.php | 202 ----------------- .../validation/src/Core/JsonEncoder.php | 20 -- .../validation/src/Core/JsonProperty.php | 13 -- .../validation/src/Core/JsonSerializer.php | 190 ---------------- .../validation/src/Core/SerializableType.php | 179 --------------- .../validation/src/Core/Types/ArrayType.php | 16 ++ .../validation/src/Core/Types/Constant.php | 12 ++ .../validation/src/Core/Types/DateType.php | 16 ++ .../validation/src/Core/Types/Union.php | 62 ++++++ seed/php-model/validation/src/Core/Union.php | 62 ------ seed/php-model/validation/src/Core/Utils.php | 61 ------ seed/php-model/validation/src/Type.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../validation/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../variables/src/Core/ArrayType.php | 16 -- .../php-model/variables/src/Core/Constant.php | 12 -- .../php-model/variables/src/Core/DateType.php | 16 -- .../variables/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../variables/src/Core/Json/JsonEncoder.php | 20 ++ .../variables/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../variables/src/Core/Json/Utils.php | 61 ++++++ .../variables/src/Core/JsonDecoder.php | 160 -------------- .../variables/src/Core/JsonDeserializer.php | 202 ----------------- .../variables/src/Core/JsonEncoder.php | 20 -- .../variables/src/Core/JsonProperty.php | 13 -- .../variables/src/Core/JsonSerializer.php | 190 ---------------- .../variables/src/Core/SerializableType.php | 179 --------------- .../variables/src/Core/Types/ArrayType.php | 16 ++ .../variables/src/Core/Types/Constant.php | 12 ++ .../variables/src/Core/Types/DateType.php | 16 ++ .../variables/src/Core/Types/Union.php | 62 ++++++ seed/php-model/variables/src/Core/Union.php | 62 ------ seed/php-model/variables/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../variables/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../version-no-default/src/Core/ArrayType.php | 16 -- .../version-no-default/src/Core/Constant.php | 12 -- .../version-no-default/src/Core/DateType.php | 16 -- .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../version-no-default/src/Core/Union.php | 62 ------ .../version-no-default/src/Core/Utils.php | 61 ------ .../version-no-default/src/User/User.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-model/version/src/Core/ArrayType.php | 16 -- seed/php-model/version/src/Core/Constant.php | 12 -- seed/php-model/version/src/Core/DateType.php | 16 -- .../version/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../version/src/Core/Json/JsonEncoder.php | 20 ++ .../version/src/Core/Json/JsonProperty.php | 13 ++ .../version/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-model/version/src/Core/Json/Utils.php | 61 ++++++ .../version/src/Core/JsonDecoder.php | 160 -------------- .../version/src/Core/JsonDeserializer.php | 202 ----------------- .../version/src/Core/JsonEncoder.php | 20 -- .../version/src/Core/JsonProperty.php | 13 -- .../version/src/Core/JsonSerializer.php | 190 ---------------- .../version/src/Core/SerializableType.php | 179 --------------- .../version/src/Core/Types/ArrayType.php | 16 ++ .../version/src/Core/Types/Constant.php | 12 ++ .../version/src/Core/Types/DateType.php | 16 ++ .../version/src/Core/Types/Union.php | 62 ++++++ seed/php-model/version/src/Core/Union.php | 62 ------ seed/php-model/version/src/Core/Utils.php | 61 ------ seed/php-model/version/src/User/User.php | 4 +- .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../version/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../version/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../version/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../websocket/src/Core/ArrayType.php | 16 -- .../php-model/websocket/src/Core/Constant.php | 12 -- .../php-model/websocket/src/Core/DateType.php | 16 -- .../websocket/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../websocket/src/Core/Json/JsonEncoder.php | 20 ++ .../websocket/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../websocket/src/Core/Json/Utils.php | 61 ++++++ .../websocket/src/Core/JsonDecoder.php | 160 -------------- .../websocket/src/Core/JsonDeserializer.php | 202 ----------------- .../websocket/src/Core/JsonEncoder.php | 20 -- .../websocket/src/Core/JsonProperty.php | 13 -- .../websocket/src/Core/JsonSerializer.php | 190 ---------------- .../websocket/src/Core/SerializableType.php | 179 --------------- .../websocket/src/Core/Types/ArrayType.php | 16 ++ .../websocket/src/Core/Types/Constant.php | 12 ++ .../websocket/src/Core/Types/DateType.php | 16 ++ .../websocket/src/Core/Types/Union.php | 62 ++++++ seed/php-model/websocket/src/Core/Union.php | 62 ------ seed/php-model/websocket/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../websocket/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../alias-extends/src/Core/ArrayType.php | 16 -- .../alias-extends/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../alias-extends/src/Core/Constant.php | 12 -- .../alias-extends/src/Core/DateType.php | 16 -- .../alias-extends/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../alias-extends/src/Core/Json/Utils.php | 61 ++++++ .../alias-extends/src/Core/JsonApiRequest.php | 25 --- .../alias-extends/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../alias-extends/src/Core/JsonEncoder.php | 20 -- .../alias-extends/src/Core/JsonProperty.php | 13 -- .../alias-extends/src/Core/JsonSerializer.php | 190 ---------------- .../alias-extends/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../alias-extends/src/Core/Types/Constant.php | 12 ++ .../alias-extends/src/Core/Types/DateType.php | 16 ++ .../alias-extends/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/alias-extends/src/Core/Union.php | 62 ------ seed/php-sdk/alias-extends/src/Core/Utils.php | 61 ------ .../src/Requests/InlinedChildRequest.php | 4 +- seed/php-sdk/alias-extends/src/SeedClient.php | 6 +- .../php-sdk/alias-extends/src/Types/Child.php | 4 +- .../alias-extends/src/Types/Parent_.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/alias/src/Core/ArrayType.php | 16 -- .../php-sdk/alias/src/Core/BaseApiRequest.php | 22 -- .../alias/src/Core/Client/BaseApiRequest.php | 22 ++ .../alias/src/Core/Client/HttpMethod.php | 12 ++ .../alias/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/alias/src/Core/Constant.php | 12 -- seed/php-sdk/alias/src/Core/DateType.php | 16 -- seed/php-sdk/alias/src/Core/HttpMethod.php | 12 -- .../alias/src/Core/Json/JsonApiRequest.php | 28 +++ .../alias/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../alias/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../alias/src/Core/Json/JsonEncoder.php | 20 ++ .../alias/src/Core/Json/JsonProperty.php | 13 ++ .../alias/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../alias/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/alias/src/Core/Json/Utils.php | 61 ++++++ .../php-sdk/alias/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/alias/src/Core/JsonDecoder.php | 160 -------------- .../alias/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/alias/src/Core/JsonEncoder.php | 20 -- seed/php-sdk/alias/src/Core/JsonProperty.php | 13 -- .../php-sdk/alias/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/alias/src/Core/RawClient.php | 138 ------------ .../alias/src/Core/SerializableType.php | 179 --------------- .../alias/src/Core/Types/ArrayType.php | 16 ++ .../php-sdk/alias/src/Core/Types/Constant.php | 12 ++ .../php-sdk/alias/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/alias/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/alias/src/Core/Union.php | 62 ------ seed/php-sdk/alias/src/Core/Utils.php | 61 ------ seed/php-sdk/alias/src/SeedClient.php | 6 +- seed/php-sdk/alias/src/Types/Type.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../alias/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../alias/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../alias/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../alias/tests/Seed/Core/RawClientTest.php | 101 --------- .../alias/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../alias/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/any-auth/src/Auth/AuthClient.php | 6 +- .../src/Auth/Requests/GetTokenRequest.php | 4 +- .../any-auth/src/Auth/Types/TokenResponse.php | 4 +- seed/php-sdk/any-auth/src/Core/ArrayType.php | 16 -- .../any-auth/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../any-auth/src/Core/Client/HttpMethod.php | 12 ++ .../any-auth/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/any-auth/src/Core/Constant.php | 12 -- seed/php-sdk/any-auth/src/Core/DateType.php | 16 -- seed/php-sdk/any-auth/src/Core/HttpMethod.php | 12 -- .../any-auth/src/Core/Json/JsonApiRequest.php | 28 +++ .../any-auth/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../any-auth/src/Core/Json/JsonEncoder.php | 20 ++ .../any-auth/src/Core/Json/JsonProperty.php | 13 ++ .../any-auth/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/any-auth/src/Core/Json/Utils.php | 61 ++++++ .../any-auth/src/Core/JsonApiRequest.php | 25 --- .../php-sdk/any-auth/src/Core/JsonDecoder.php | 160 -------------- .../any-auth/src/Core/JsonDeserializer.php | 202 ----------------- .../php-sdk/any-auth/src/Core/JsonEncoder.php | 20 -- .../any-auth/src/Core/JsonProperty.php | 13 -- .../any-auth/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/any-auth/src/Core/RawClient.php | 138 ------------ .../any-auth/src/Core/SerializableType.php | 179 --------------- .../any-auth/src/Core/Types/ArrayType.php | 16 ++ .../any-auth/src/Core/Types/Constant.php | 12 ++ .../any-auth/src/Core/Types/DateType.php | 16 ++ .../php-sdk/any-auth/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/any-auth/src/Core/Union.php | 62 ------ seed/php-sdk/any-auth/src/Core/Utils.php | 61 ------ seed/php-sdk/any-auth/src/SeedClient.php | 2 +- seed/php-sdk/any-auth/src/User/Types/User.php | 4 +- seed/php-sdk/any-auth/src/User/UserClient.php | 8 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../any-auth/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../any-auth/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../api-wide-base-path/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../api-wide-base-path/src/Core/Constant.php | 12 -- .../api-wide-base-path/src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../api-wide-base-path/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../api-wide-base-path/src/Core/Union.php | 62 ------ .../api-wide-base-path/src/Core/Utils.php | 61 ------ .../api-wide-base-path/src/SeedClient.php | 2 +- .../src/Service/ServiceClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/audiences/src/Core/ArrayType.php | 16 -- .../audiences/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../audiences/src/Core/Client/HttpMethod.php | 12 ++ .../audiences/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/audiences/src/Core/Constant.php | 12 -- seed/php-sdk/audiences/src/Core/DateType.php | 16 -- .../php-sdk/audiences/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../audiences/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../audiences/src/Core/Json/JsonEncoder.php | 20 ++ .../audiences/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-sdk/audiences/src/Core/Json/Utils.php | 61 ++++++ .../audiences/src/Core/JsonApiRequest.php | 25 --- .../audiences/src/Core/JsonDecoder.php | 160 -------------- .../audiences/src/Core/JsonDeserializer.php | 202 ----------------- .../audiences/src/Core/JsonEncoder.php | 20 -- .../audiences/src/Core/JsonProperty.php | 13 -- .../audiences/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/audiences/src/Core/RawClient.php | 138 ------------ .../audiences/src/Core/SerializableType.php | 179 --------------- .../audiences/src/Core/Types/ArrayType.php | 16 ++ .../audiences/src/Core/Types/Constant.php | 12 ++ .../audiences/src/Core/Types/DateType.php | 16 ++ .../audiences/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/audiences/src/Core/Union.php | 62 ------ seed/php-sdk/audiences/src/Core/Utils.php | 61 ------ .../audiences/src/FolderA/FolderAClient.php | 2 +- .../src/FolderA/Service/ServiceClient.php | 6 +- .../src/FolderA/Service/Types/Response.php | 4 +- .../src/FolderB/Common/Types/Foo.php | 4 +- .../src/FolderC/Common/Types/FolderCFoo.php | 4 +- .../audiences/src/FolderD/FolderDClient.php | 2 +- .../src/FolderD/Service/ServiceClient.php | 6 +- .../src/FolderD/Service/Types/Response.php | 4 +- seed/php-sdk/audiences/src/Foo/FooClient.php | 6 +- .../src/Foo/Requests/FindRequest.php | 4 +- .../audiences/src/Foo/Types/FilteredType.php | 4 +- .../audiences/src/Foo/Types/ImportingType.php | 4 +- seed/php-sdk/audiences/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../audiences/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/SeedClient.php | 2 +- .../Service/Requests/HeaderAuthRequest.php | 2 +- .../src/Service/ServiceClient.php | 8 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/BasicAuth/BasicAuthClient.php | 8 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../Types/UnauthorizedRequestErrorBody.php | 4 +- .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/BasicAuth/BasicAuthClient.php | 8 +- .../php-sdk/basic-auth/src/Core/ArrayType.php | 16 -- .../basic-auth/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../basic-auth/src/Core/Client/HttpMethod.php | 12 ++ .../basic-auth/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/basic-auth/src/Core/Constant.php | 12 -- seed/php-sdk/basic-auth/src/Core/DateType.php | 16 -- .../basic-auth/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../basic-auth/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../basic-auth/src/Core/Json/JsonEncoder.php | 20 ++ .../basic-auth/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../basic-auth/src/Core/Json/Utils.php | 61 ++++++ .../basic-auth/src/Core/JsonApiRequest.php | 25 --- .../basic-auth/src/Core/JsonDecoder.php | 160 -------------- .../basic-auth/src/Core/JsonDeserializer.php | 202 ----------------- .../basic-auth/src/Core/JsonEncoder.php | 20 -- .../basic-auth/src/Core/JsonProperty.php | 13 -- .../basic-auth/src/Core/JsonSerializer.php | 190 ---------------- .../php-sdk/basic-auth/src/Core/RawClient.php | 138 ------------ .../basic-auth/src/Core/SerializableType.php | 179 --------------- .../basic-auth/src/Core/Types/ArrayType.php | 16 ++ .../basic-auth/src/Core/Types/Constant.php | 12 ++ .../basic-auth/src/Core/Types/DateType.php | 16 ++ .../basic-auth/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/basic-auth/src/Core/Union.php | 62 ------ seed/php-sdk/basic-auth/src/Core/Utils.php | 61 ------ .../Types/UnauthorizedRequestErrorBody.php | 4 +- seed/php-sdk/basic-auth/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../basic-auth/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/SeedClient.php | 2 +- .../src/Service/ServiceClient.php | 8 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/bytes/src/Core/ArrayType.php | 16 -- .../php-sdk/bytes/src/Core/BaseApiRequest.php | 22 -- .../bytes/src/Core/Client/BaseApiRequest.php | 22 ++ .../bytes/src/Core/Client/HttpMethod.php | 12 ++ .../bytes/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/bytes/src/Core/Constant.php | 12 -- seed/php-sdk/bytes/src/Core/DateType.php | 16 -- seed/php-sdk/bytes/src/Core/HttpMethod.php | 12 -- .../bytes/src/Core/Json/JsonApiRequest.php | 28 +++ .../bytes/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../bytes/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../bytes/src/Core/Json/JsonEncoder.php | 20 ++ .../bytes/src/Core/Json/JsonProperty.php | 13 ++ .../bytes/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../bytes/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/bytes/src/Core/Json/Utils.php | 61 ++++++ .../php-sdk/bytes/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/bytes/src/Core/JsonDecoder.php | 160 -------------- .../bytes/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/bytes/src/Core/JsonEncoder.php | 20 -- seed/php-sdk/bytes/src/Core/JsonProperty.php | 13 -- .../php-sdk/bytes/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/bytes/src/Core/RawClient.php | 138 ------------ .../bytes/src/Core/SerializableType.php | 179 --------------- .../bytes/src/Core/Types/ArrayType.php | 16 ++ .../php-sdk/bytes/src/Core/Types/Constant.php | 12 ++ .../php-sdk/bytes/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/bytes/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/bytes/src/Core/Union.php | 62 ------ seed/php-sdk/bytes/src/Core/Utils.php | 61 ------ seed/php-sdk/bytes/src/SeedClient.php | 2 +- .../bytes/src/Service/ServiceClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../bytes/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../bytes/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../bytes/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../bytes/tests/Seed/Core/RawClientTest.php | 101 --------- .../bytes/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../bytes/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/A/Types/A.php | 2 +- .../src/Ast/Types/ObjectFieldValue.php | 4 +- .../src/Ast/Types/ObjectValue.php | 2 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/Types/ImportingA.php | 4 +- .../src/Types/RootType.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../circular-references/src/A/Types/A.php | 2 +- .../src/Ast/Types/ObjectValue.php | 2 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../circular-references/src/Core/Constant.php | 12 -- .../circular-references/src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../circular-references/src/Core/Union.php | 62 ------ .../circular-references/src/Core/Utils.php | 61 ------ .../src/Types/ImportingA.php | 4 +- .../src/Types/RootType.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/FolderA/FolderAClient.php | 2 +- .../src/FolderA/Service/ServiceClient.php | 6 +- .../src/FolderA/Service/Types/Response.php | 4 +- .../src/FolderB/Common/Types/Foo.php | 4 +- .../src/FolderC/Common/Types/Foo.php | 4 +- .../src/FolderD/FolderDClient.php | 2 +- .../src/FolderD/Service/ServiceClient.php | 6 +- .../src/FolderD/Service/Types/Response.php | 4 +- .../src/Foo/FooClient.php | 6 +- .../src/Foo/Requests/FindRequest.php | 4 +- .../src/Foo/Types/ImportingType.php | 4 +- .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../custom-auth/src/Core/ArrayType.php | 16 -- .../custom-auth/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../custom-auth/src/Core/Client/RawClient.php | 139 ++++++++++++ .../php-sdk/custom-auth/src/Core/Constant.php | 12 -- .../php-sdk/custom-auth/src/Core/DateType.php | 16 -- .../custom-auth/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../custom-auth/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../custom-auth/src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../custom-auth/src/Core/Json/Utils.php | 61 ++++++ .../custom-auth/src/Core/JsonApiRequest.php | 25 --- .../custom-auth/src/Core/JsonDecoder.php | 160 -------------- .../custom-auth/src/Core/JsonDeserializer.php | 202 ----------------- .../custom-auth/src/Core/JsonEncoder.php | 20 -- .../custom-auth/src/Core/JsonProperty.php | 13 -- .../custom-auth/src/Core/JsonSerializer.php | 190 ---------------- .../custom-auth/src/Core/RawClient.php | 138 ------------ .../custom-auth/src/Core/SerializableType.php | 179 --------------- .../custom-auth/src/Core/Types/ArrayType.php | 16 ++ .../custom-auth/src/Core/Types/Constant.php | 12 ++ .../custom-auth/src/Core/Types/DateType.php | 16 ++ .../custom-auth/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/custom-auth/src/Core/Union.php | 62 ------ seed/php-sdk/custom-auth/src/Core/Utils.php | 61 ------ .../src/CustomAuth/CustomAuthClient.php | 8 +- .../Types/UnauthorizedRequestErrorBody.php | 4 +- seed/php-sdk/custom-auth/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../custom-auth/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/enum/src/Core/ArrayType.php | 16 -- seed/php-sdk/enum/src/Core/BaseApiRequest.php | 22 -- .../enum/src/Core/Client/BaseApiRequest.php | 22 ++ .../enum/src/Core/Client/HttpMethod.php | 12 ++ .../enum/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/enum/src/Core/Constant.php | 12 -- seed/php-sdk/enum/src/Core/DateType.php | 16 -- seed/php-sdk/enum/src/Core/HttpMethod.php | 12 -- .../enum/src/Core/Json/JsonApiRequest.php | 28 +++ .../enum/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../enum/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../enum/src/Core/Json/JsonEncoder.php | 20 ++ .../enum/src/Core/Json/JsonProperty.php | 13 ++ .../enum/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../enum/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/enum/src/Core/Json/Utils.php | 61 ++++++ seed/php-sdk/enum/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/enum/src/Core/JsonDecoder.php | 160 -------------- .../enum/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/enum/src/Core/JsonEncoder.php | 20 -- seed/php-sdk/enum/src/Core/JsonProperty.php | 13 -- seed/php-sdk/enum/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/enum/src/Core/RawClient.php | 138 ------------ .../enum/src/Core/SerializableType.php | 179 --------------- .../php-sdk/enum/src/Core/Types/ArrayType.php | 16 ++ seed/php-sdk/enum/src/Core/Types/Constant.php | 12 ++ seed/php-sdk/enum/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/enum/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/enum/src/Core/Union.php | 62 ------ seed/php-sdk/enum/src/Core/Utils.php | 61 ------ .../InlinedRequest/InlinedRequestClient.php | 6 +- .../Requests/SendEnumInlinedRequest.php | 4 +- .../enum/src/PathParam/PathParamClient.php | 6 +- .../enum/src/QueryParam/QueryParamClient.php | 6 +- .../Requests/SendEnumAsQueryParamRequest.php | 2 +- .../SendEnumListAsQueryParamRequest.php | 2 +- seed/php-sdk/enum/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../enum/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../php-sdk/enum/tests/Seed/Core/EnumTest.php | 76 ------- .../enum/tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../enum/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../enum/tests/Seed/Core/RawClientTest.php | 101 --------- .../enum/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../enum/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../error-property/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../error-property/src/Core/Constant.php | 12 -- .../error-property/src/Core/DateType.php | 16 -- .../error-property/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../error-property/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../error-property/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../error-property/src/Core/JsonEncoder.php | 20 -- .../error-property/src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../error-property/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../error-property/src/Core/Types/Union.php | 62 ++++++ .../php-sdk/error-property/src/Core/Union.php | 62 ------ .../php-sdk/error-property/src/Core/Utils.php | 61 ------ .../Types/PropertyBasedErrorTestBody.php | 4 +- .../PropertyBasedErrorClient.php | 8 +- .../php-sdk/error-property/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Commons/Types/Types/Metadata.php | 6 +- seed/php-sdk/examples/src/Core/ArrayType.php | 16 -- .../examples/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../examples/src/Core/Client/HttpMethod.php | 12 ++ .../examples/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/examples/src/Core/Constant.php | 12 -- seed/php-sdk/examples/src/Core/DateType.php | 16 -- seed/php-sdk/examples/src/Core/HttpMethod.php | 12 -- .../examples/src/Core/Json/JsonApiRequest.php | 28 +++ .../examples/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../examples/src/Core/Json/JsonEncoder.php | 20 ++ .../examples/src/Core/Json/JsonProperty.php | 13 ++ .../examples/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/examples/src/Core/Json/Utils.php | 61 ++++++ .../examples/src/Core/JsonApiRequest.php | 25 --- .../php-sdk/examples/src/Core/JsonDecoder.php | 160 -------------- .../examples/src/Core/JsonDeserializer.php | 202 ----------------- .../php-sdk/examples/src/Core/JsonEncoder.php | 20 -- .../examples/src/Core/JsonProperty.php | 13 -- .../examples/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/examples/src/Core/RawClient.php | 138 ------------ .../examples/src/Core/SerializableType.php | 179 --------------- .../examples/src/Core/Types/ArrayType.php | 16 ++ .../examples/src/Core/Types/Constant.php | 12 ++ .../examples/src/Core/Types/DateType.php | 16 ++ .../php-sdk/examples/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/examples/src/Core/Union.php | 62 ------ seed/php-sdk/examples/src/Core/Utils.php | 61 ------ seed/php-sdk/examples/src/File/FileClient.php | 2 +- .../File/Notification/NotificationClient.php | 2 +- .../Notification/Service/ServiceClient.php | 8 +- .../File/Service/Requests/GetFileRequest.php | 2 +- .../src/File/Service/ServiceClient.php | 6 +- .../examples/src/Health/HealthClient.php | 2 +- .../src/Health/Service/ServiceClient.php | 8 +- seed/php-sdk/examples/src/SeedClient.php | 8 +- .../Service/Requests/GetMetadataRequest.php | 2 +- .../examples/src/Service/ServiceClient.php | 8 +- .../php-sdk/examples/src/Types/Identifier.php | 4 +- .../examples/src/Types/Types/Actor.php | 4 +- .../examples/src/Types/Types/Actress.php | 4 +- .../examples/src/Types/Types/Directory.php | 6 +- .../examples/src/Types/Types/Entity.php | 4 +- .../src/Types/Types/ExceptionInfo.php | 4 +- .../src/Types/Types/ExtendedMovie.php | 6 +- .../php-sdk/examples/src/Types/Types/File.php | 4 +- .../examples/src/Types/Types/Migration.php | 4 +- .../examples/src/Types/Types/Moment.php | 6 +- .../examples/src/Types/Types/Movie.php | 6 +- .../php-sdk/examples/src/Types/Types/Node.php | 6 +- .../examples/src/Types/Types/Request.php | 4 +- .../examples/src/Types/Types/Response.php | 6 +- .../examples/src/Types/Types/ResponseType.php | 4 +- .../examples/src/Types/Types/StuntDouble.php | 4 +- .../php-sdk/examples/src/Types/Types/Tree.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../examples/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../examples/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../php-sdk/exhaustive/src/Core/ArrayType.php | 16 -- .../exhaustive/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../exhaustive/src/Core/Client/HttpMethod.php | 12 ++ .../exhaustive/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/exhaustive/src/Core/Constant.php | 12 -- seed/php-sdk/exhaustive/src/Core/DateType.php | 16 -- .../exhaustive/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../exhaustive/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../exhaustive/src/Core/Json/JsonEncoder.php | 20 ++ .../exhaustive/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../exhaustive/src/Core/Json/Utils.php | 61 ++++++ .../exhaustive/src/Core/JsonApiRequest.php | 25 --- .../exhaustive/src/Core/JsonDecoder.php | 160 -------------- .../exhaustive/src/Core/JsonDeserializer.php | 202 ----------------- .../exhaustive/src/Core/JsonEncoder.php | 20 -- .../exhaustive/src/Core/JsonProperty.php | 13 -- .../exhaustive/src/Core/JsonSerializer.php | 190 ---------------- .../php-sdk/exhaustive/src/Core/RawClient.php | 138 ------------ .../exhaustive/src/Core/SerializableType.php | 179 --------------- .../exhaustive/src/Core/Types/ArrayType.php | 16 ++ .../exhaustive/src/Core/Types/Constant.php | 12 ++ .../exhaustive/src/Core/Types/DateType.php | 16 ++ .../exhaustive/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/exhaustive/src/Core/Union.php | 62 ------ seed/php-sdk/exhaustive/src/Core/Utils.php | 61 ------ .../Endpoints/Container/ContainerClient.php | 10 +- .../src/Endpoints/EndpointsClient.php | 2 +- .../src/Endpoints/Enum/EnumClient.php | 8 +- .../HttpMethods/HttpMethodsClient.php | 8 +- .../src/Endpoints/Object/ObjectClient.php | 8 +- .../src/Endpoints/Params/ParamsClient.php | 8 +- .../Params/Requests/GetWithMultipleQuery.php | 2 +- .../Params/Requests/GetWithPathAndQuery.php | 2 +- .../Params/Requests/GetWithQuery.php | 2 +- .../Endpoints/Primitive/PrimitiveClient.php | 10 +- .../src/Endpoints/Union/UnionClient.php | 8 +- .../Types/BadObjectRequestInfo.php | 4 +- .../InlinedRequests/InlinedRequestsClient.php | 6 +- .../Requests/PostWithObjectBody.php | 4 +- .../exhaustive/src/NoAuth/NoAuthClient.php | 8 +- .../src/NoReqBody/NoReqBodyClient.php | 8 +- .../ReqWithHeaders/ReqWithHeadersClient.php | 6 +- .../Requests/ReqWithHeaders.php | 2 +- seed/php-sdk/exhaustive/src/SeedClient.php | 2 +- .../src/Types/Object/Types/DoubleOptional.php | 4 +- .../Types/NestedObjectWithOptionalField.php | 4 +- .../Types/NestedObjectWithRequiredField.php | 4 +- .../Types/Object/Types/ObjectWithMapOfMap.php | 6 +- .../Object/Types/ObjectWithOptionalField.php | 8 +- .../Object/Types/ObjectWithRequiredField.php | 4 +- .../exhaustive/src/Types/Union/Types/Cat.php | 4 +- .../exhaustive/src/Types/Union/Types/Dog.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../exhaustive/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/extends/src/Core/ArrayType.php | 16 -- .../extends/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../extends/src/Core/Client/HttpMethod.php | 12 ++ .../extends/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/extends/src/Core/Constant.php | 12 -- seed/php-sdk/extends/src/Core/DateType.php | 16 -- seed/php-sdk/extends/src/Core/HttpMethod.php | 12 -- .../extends/src/Core/Json/JsonApiRequest.php | 28 +++ .../extends/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../extends/src/Core/Json/JsonEncoder.php | 20 ++ .../extends/src/Core/Json/JsonProperty.php | 13 ++ .../extends/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/extends/src/Core/Json/Utils.php | 61 ++++++ .../extends/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/extends/src/Core/JsonDecoder.php | 160 -------------- .../extends/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/extends/src/Core/JsonEncoder.php | 20 -- .../php-sdk/extends/src/Core/JsonProperty.php | 13 -- .../extends/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/extends/src/Core/RawClient.php | 138 ------------ .../extends/src/Core/SerializableType.php | 179 --------------- .../extends/src/Core/Types/ArrayType.php | 16 ++ .../extends/src/Core/Types/Constant.php | 12 ++ .../extends/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/extends/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/extends/src/Core/Union.php | 62 ------ seed/php-sdk/extends/src/Core/Utils.php | 61 ------ seed/php-sdk/extends/src/Requests/Inlined.php | 4 +- seed/php-sdk/extends/src/SeedClient.php | 6 +- seed/php-sdk/extends/src/Types/Docs.php | 4 +- .../php-sdk/extends/src/Types/ExampleType.php | 4 +- seed/php-sdk/extends/src/Types/Json.php | 4 +- seed/php-sdk/extends/src/Types/NestedType.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../extends/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../extends/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../extends/tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../extends/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../extra-properties/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../extra-properties/src/Core/Constant.php | 12 -- .../extra-properties/src/Core/DateType.php | 16 -- .../extra-properties/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../extra-properties/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../extra-properties/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../extra-properties/src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../extra-properties/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../extra-properties/src/Core/Types/Union.php | 62 ++++++ .../extra-properties/src/Core/Union.php | 62 ------ .../extra-properties/src/Core/Utils.php | 61 ------ .../extra-properties/src/SeedClient.php | 2 +- .../extra-properties/src/Types/Failure.php | 4 +- .../src/User/Requests/CreateUserRequest.php | 4 +- .../extra-properties/src/User/Types/User.php | 4 +- .../extra-properties/src/User/UserClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../file-download/src/Core/ArrayType.php | 16 -- .../file-download/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../file-download/src/Core/Constant.php | 12 -- .../file-download/src/Core/DateType.php | 16 -- .../file-download/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../file-download/src/Core/Json/Utils.php | 61 ++++++ .../file-download/src/Core/JsonApiRequest.php | 25 --- .../file-download/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../file-download/src/Core/JsonEncoder.php | 20 -- .../file-download/src/Core/JsonProperty.php | 13 -- .../file-download/src/Core/JsonSerializer.php | 190 ---------------- .../file-download/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../file-download/src/Core/Types/Constant.php | 12 ++ .../file-download/src/Core/Types/DateType.php | 16 ++ .../file-download/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/file-download/src/Core/Union.php | 62 ------ seed/php-sdk/file-download/src/Core/Utils.php | 61 ------ seed/php-sdk/file-download/src/SeedClient.php | 2 +- .../src/Service/ServiceClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../file-upload/src/Core/ArrayType.php | 16 -- .../file-upload/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../file-upload/src/Core/Client/RawClient.php | 139 ++++++++++++ .../php-sdk/file-upload/src/Core/Constant.php | 12 -- .../php-sdk/file-upload/src/Core/DateType.php | 16 -- .../file-upload/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../file-upload/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../file-upload/src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../file-upload/src/Core/Json/Utils.php | 61 ++++++ .../file-upload/src/Core/JsonApiRequest.php | 25 --- .../file-upload/src/Core/JsonDecoder.php | 160 -------------- .../file-upload/src/Core/JsonDeserializer.php | 202 ----------------- .../file-upload/src/Core/JsonEncoder.php | 20 -- .../file-upload/src/Core/JsonProperty.php | 13 -- .../file-upload/src/Core/JsonSerializer.php | 190 ---------------- .../file-upload/src/Core/RawClient.php | 138 ------------ .../file-upload/src/Core/SerializableType.php | 179 --------------- .../file-upload/src/Core/Types/ArrayType.php | 16 ++ .../file-upload/src/Core/Types/Constant.php | 12 ++ .../file-upload/src/Core/Types/DateType.php | 16 ++ .../file-upload/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/file-upload/src/Core/Union.php | 62 ------ seed/php-sdk/file-upload/src/Core/Utils.php | 61 ------ seed/php-sdk/file-upload/src/SeedClient.php | 2 +- .../src/Service/Requests/JustFileRequet.php | 2 +- .../JustFileWithQueryParamsRequet.php | 2 +- .../src/Service/Requests/MyRequest.php | 2 +- .../Requests/WithContentTypeRequest.php | 2 +- .../file-upload/src/Service/ServiceClient.php | 6 +- .../src/Service/Types/MyObject.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../file-upload/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/folders/src/A/AClient.php | 2 +- seed/php-sdk/folders/src/A/B/BClient.php | 6 +- seed/php-sdk/folders/src/A/C/CClient.php | 6 +- seed/php-sdk/folders/src/Core/ArrayType.php | 16 -- .../folders/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../folders/src/Core/Client/HttpMethod.php | 12 ++ .../folders/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/folders/src/Core/Constant.php | 12 -- seed/php-sdk/folders/src/Core/DateType.php | 16 -- seed/php-sdk/folders/src/Core/HttpMethod.php | 12 -- .../folders/src/Core/Json/JsonApiRequest.php | 28 +++ .../folders/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../folders/src/Core/Json/JsonEncoder.php | 20 ++ .../folders/src/Core/Json/JsonProperty.php | 13 ++ .../folders/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/folders/src/Core/Json/Utils.php | 61 ++++++ .../folders/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/folders/src/Core/JsonDecoder.php | 160 -------------- .../folders/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/folders/src/Core/JsonEncoder.php | 20 -- .../php-sdk/folders/src/Core/JsonProperty.php | 13 -- .../folders/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/folders/src/Core/RawClient.php | 138 ------------ .../folders/src/Core/SerializableType.php | 179 --------------- .../folders/src/Core/Types/ArrayType.php | 16 ++ .../folders/src/Core/Types/Constant.php | 12 ++ .../folders/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/folders/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/folders/src/Core/Union.php | 62 ------ seed/php-sdk/folders/src/Core/Utils.php | 61 ------ .../folders/src/Folder/FolderClient.php | 6 +- .../src/Folder/Service/ServiceClient.php | 6 +- seed/php-sdk/folders/src/SeedClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../folders/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../folders/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../folders/tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../folders/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../grpc-proto-exhaustive/src/Core/Union.php | 62 ------ .../grpc-proto-exhaustive/src/Core/Utils.php | 61 ------ .../src/Dataservice/DataserviceClient.php | 6 +- .../Dataservice/Requests/DeleteRequest.php | 8 +- .../Dataservice/Requests/DescribeRequest.php | 6 +- .../src/Dataservice/Requests/FetchRequest.php | 2 +- .../src/Dataservice/Requests/ListRequest.php | 2 +- .../src/Dataservice/Requests/QueryRequest.php | 8 +- .../Dataservice/Requests/UpdateRequest.php | 8 +- .../Dataservice/Requests/UploadRequest.php | 6 +- .../grpc-proto-exhaustive/src/SeedClient.php | 2 +- .../src/Types/Column.php | 8 +- .../src/Types/DeleteResponse.php | 2 +- .../src/Types/DescribeResponse.php | 6 +- .../src/Types/FetchResponse.php | 6 +- .../src/Types/IndexedData.php | 6 +- .../src/Types/ListElement.php | 4 +- .../src/Types/ListResponse.php | 6 +- .../src/Types/NamespaceSummary.php | 4 +- .../src/Types/Pagination.php | 4 +- .../src/Types/QueryColumn.php | 8 +- .../src/Types/QueryResponse.php | 6 +- .../src/Types/QueryResult.php | 6 +- .../src/Types/ScoredColumn.php | 8 +- .../src/Types/UpdateResponse.php | 2 +- .../src/Types/UploadResponse.php | 4 +- .../grpc-proto-exhaustive/src/Types/Usage.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../php-sdk/grpc-proto/src/Core/ArrayType.php | 16 -- .../grpc-proto/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../grpc-proto/src/Core/Client/HttpMethod.php | 12 ++ .../grpc-proto/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/grpc-proto/src/Core/Constant.php | 12 -- seed/php-sdk/grpc-proto/src/Core/DateType.php | 16 -- .../grpc-proto/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../grpc-proto/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../grpc-proto/src/Core/Json/JsonEncoder.php | 20 ++ .../grpc-proto/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../grpc-proto/src/Core/Json/Utils.php | 61 ++++++ .../grpc-proto/src/Core/JsonApiRequest.php | 25 --- .../grpc-proto/src/Core/JsonDecoder.php | 160 -------------- .../grpc-proto/src/Core/JsonDeserializer.php | 202 ----------------- .../grpc-proto/src/Core/JsonEncoder.php | 20 -- .../grpc-proto/src/Core/JsonProperty.php | 13 -- .../grpc-proto/src/Core/JsonSerializer.php | 190 ---------------- .../php-sdk/grpc-proto/src/Core/RawClient.php | 138 ------------ .../grpc-proto/src/Core/SerializableType.php | 179 --------------- .../grpc-proto/src/Core/Types/ArrayType.php | 16 ++ .../grpc-proto/src/Core/Types/Constant.php | 12 ++ .../grpc-proto/src/Core/Types/DateType.php | 16 ++ .../grpc-proto/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/grpc-proto/src/Core/Union.php | 62 ------ seed/php-sdk/grpc-proto/src/Core/Utils.php | 61 ------ seed/php-sdk/grpc-proto/src/SeedClient.php | 2 +- .../grpc-proto/src/Types/CreateResponse.php | 4 +- .../grpc-proto/src/Types/UserModel.php | 6 +- .../Userservice/Requests/CreateRequest.php | 6 +- .../src/Userservice/UserserviceClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../grpc-proto/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../idempotency-headers/src/Core/Constant.php | 12 -- .../idempotency-headers/src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../idempotency-headers/src/Core/Union.php | 62 ------ .../idempotency-headers/src/Core/Utils.php | 61 ------ .../src/Payment/PaymentClient.php | 8 +- .../Payment/Requests/CreatePaymentRequest.php | 4 +- .../idempotency-headers/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/imdb/src/Core/ArrayType.php | 16 -- seed/php-sdk/imdb/src/Core/BaseApiRequest.php | 22 -- .../imdb/src/Core/Client/BaseApiRequest.php | 22 ++ .../imdb/src/Core/Client/HttpMethod.php | 12 ++ .../imdb/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/imdb/src/Core/Constant.php | 12 -- seed/php-sdk/imdb/src/Core/DateType.php | 16 -- seed/php-sdk/imdb/src/Core/HttpMethod.php | 12 -- .../imdb/src/Core/Json/JsonApiRequest.php | 28 +++ .../imdb/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../imdb/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../imdb/src/Core/Json/JsonEncoder.php | 20 ++ .../imdb/src/Core/Json/JsonProperty.php | 13 ++ .../imdb/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../imdb/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/imdb/src/Core/Json/Utils.php | 61 ++++++ seed/php-sdk/imdb/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/imdb/src/Core/JsonDecoder.php | 160 -------------- .../imdb/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/imdb/src/Core/JsonEncoder.php | 20 -- seed/php-sdk/imdb/src/Core/JsonProperty.php | 13 -- seed/php-sdk/imdb/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/imdb/src/Core/RawClient.php | 138 ------------ .../imdb/src/Core/SerializableType.php | 179 --------------- .../php-sdk/imdb/src/Core/Types/ArrayType.php | 16 ++ seed/php-sdk/imdb/src/Core/Types/Constant.php | 12 ++ seed/php-sdk/imdb/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/imdb/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/imdb/src/Core/Union.php | 62 ------ seed/php-sdk/imdb/src/Core/Utils.php | 61 ------ seed/php-sdk/imdb/src/Imdb/ImdbClient.php | 8 +- .../src/Imdb/Types/CreateMovieRequest.php | 4 +- seed/php-sdk/imdb/src/Imdb/Types/Movie.php | 4 +- seed/php-sdk/imdb/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../imdb/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../php-sdk/imdb/tests/Seed/Core/EnumTest.php | 76 ------- .../imdb/tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../imdb/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../imdb/tests/Seed/Core/RawClientTest.php | 101 --------- .../imdb/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../imdb/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/literal/src/Core/ArrayType.php | 16 -- .../literal/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../literal/src/Core/Client/HttpMethod.php | 12 ++ .../literal/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/literal/src/Core/Constant.php | 12 -- seed/php-sdk/literal/src/Core/DateType.php | 16 -- seed/php-sdk/literal/src/Core/HttpMethod.php | 12 -- .../literal/src/Core/Json/JsonApiRequest.php | 28 +++ .../literal/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../literal/src/Core/Json/JsonEncoder.php | 20 ++ .../literal/src/Core/Json/JsonProperty.php | 13 ++ .../literal/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/literal/src/Core/Json/Utils.php | 61 ++++++ .../literal/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/literal/src/Core/JsonDecoder.php | 160 -------------- .../literal/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/literal/src/Core/JsonEncoder.php | 20 -- .../php-sdk/literal/src/Core/JsonProperty.php | 13 -- .../literal/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/literal/src/Core/RawClient.php | 138 ------------ .../literal/src/Core/SerializableType.php | 179 --------------- .../literal/src/Core/Types/ArrayType.php | 16 ++ .../literal/src/Core/Types/Constant.php | 12 ++ .../literal/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/literal/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/literal/src/Core/Union.php | 62 ------ seed/php-sdk/literal/src/Core/Utils.php | 61 ------ .../literal/src/Headers/HeadersClient.php | 6 +- .../Requests/SendLiteralsInHeadersRequest.php | 4 +- .../literal/src/Inlined/InlinedClient.php | 6 +- .../Requests/SendLiteralsInlinedRequest.php | 4 +- .../src/Inlined/Types/ANestedLiteral.php | 4 +- .../src/Inlined/Types/ATopLevelLiteral.php | 4 +- seed/php-sdk/literal/src/Path/PathClient.php | 6 +- .../php-sdk/literal/src/Query/QueryClient.php | 6 +- .../Requests/SendLiteralsInQueryRequest.php | 2 +- .../literal/src/Reference/ReferenceClient.php | 6 +- .../src/Reference/Types/ContainerObject.php | 6 +- .../Types/NestedObjectWithLiterals.php | 4 +- .../src/Reference/Types/SendRequest.php | 4 +- seed/php-sdk/literal/src/SeedClient.php | 2 +- .../literal/src/Types/SendResponse.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../literal/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../literal/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../literal/tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../literal/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../php-sdk/mixed-case/src/Core/ArrayType.php | 16 -- .../mixed-case/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../mixed-case/src/Core/Client/HttpMethod.php | 12 ++ .../mixed-case/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/mixed-case/src/Core/Constant.php | 12 -- seed/php-sdk/mixed-case/src/Core/DateType.php | 16 -- .../mixed-case/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../mixed-case/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../mixed-case/src/Core/Json/JsonEncoder.php | 20 ++ .../mixed-case/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../mixed-case/src/Core/Json/Utils.php | 61 ++++++ .../mixed-case/src/Core/JsonApiRequest.php | 25 --- .../mixed-case/src/Core/JsonDecoder.php | 160 -------------- .../mixed-case/src/Core/JsonDeserializer.php | 202 ----------------- .../mixed-case/src/Core/JsonEncoder.php | 20 -- .../mixed-case/src/Core/JsonProperty.php | 13 -- .../mixed-case/src/Core/JsonSerializer.php | 190 ---------------- .../php-sdk/mixed-case/src/Core/RawClient.php | 138 ------------ .../mixed-case/src/Core/SerializableType.php | 179 --------------- .../mixed-case/src/Core/Types/ArrayType.php | 16 ++ .../mixed-case/src/Core/Types/Constant.php | 12 ++ .../mixed-case/src/Core/Types/DateType.php | 16 ++ .../mixed-case/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/mixed-case/src/Core/Union.php | 62 ------ seed/php-sdk/mixed-case/src/Core/Utils.php | 61 ------ seed/php-sdk/mixed-case/src/SeedClient.php | 2 +- .../Service/Requests/ListResourcesRequest.php | 2 +- .../mixed-case/src/Service/ServiceClient.php | 10 +- .../src/Service/Types/NestedUser.php | 4 +- .../src/Service/Types/Organization.php | 4 +- .../mixed-case/src/Service/Types/User.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../mixed-case/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../mixed-file-directory/src/Core/Union.php | 62 ------ .../mixed-file-directory/src/Core/Utils.php | 61 ------ .../src/Organization/OrganizationClient.php | 6 +- .../Types/CreateOrganizationRequest.php | 4 +- .../src/Organization/Types/Organization.php | 6 +- .../mixed-file-directory/src/SeedClient.php | 2 +- .../src/User/Events/EventsClient.php | 8 +- .../User/Events/Metadata/MetadataClient.php | 6 +- .../Requests/GetEventMetadataRequest.php | 2 +- .../User/Events/Metadata/Types/Metadata.php | 4 +- .../Events/Requests/ListUserEventsRequest.php | 2 +- .../src/User/Events/Types/Event.php | 4 +- .../src/User/Requests/ListUsersRequest.php | 2 +- .../src/User/Types/User.php | 4 +- .../src/User/UserClient.php | 8 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../multi-line-docs/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../multi-line-docs/src/Core/Constant.php | 12 -- .../multi-line-docs/src/Core/DateType.php | 16 -- .../multi-line-docs/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../multi-line-docs/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../multi-line-docs/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../multi-line-docs/src/Core/JsonEncoder.php | 20 -- .../multi-line-docs/src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../multi-line-docs/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../multi-line-docs/src/Core/Types/Union.php | 62 ++++++ .../multi-line-docs/src/Core/Union.php | 62 ------ .../multi-line-docs/src/Core/Utils.php | 61 ------ .../multi-line-docs/src/SeedClient.php | 2 +- .../src/User/Requests/CreateUserRequest.php | 4 +- .../multi-line-docs/src/User/Types/User.php | 4 +- .../multi-line-docs/src/User/UserClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../no-environment/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../no-environment/src/Core/Constant.php | 12 -- .../no-environment/src/Core/DateType.php | 16 -- .../no-environment/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../no-environment/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../no-environment/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../no-environment/src/Core/JsonEncoder.php | 20 -- .../no-environment/src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../no-environment/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../no-environment/src/Core/Types/Union.php | 62 ++++++ .../php-sdk/no-environment/src/Core/Union.php | 62 ------ .../php-sdk/no-environment/src/Core/Utils.php | 61 ------ .../no-environment/src/Dummy/DummyClient.php | 8 +- .../php-sdk/no-environment/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Auth/AuthClient.php | 6 +- .../src/Auth/Requests/GetTokenRequest.php | 4 +- .../src/Auth/Types/TokenResponse.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Auth/AuthClient.php | 6 +- .../src/Auth/Requests/GetTokenRequest.php | 4 +- .../src/Auth/Requests/RefreshTokenRequest.php | 4 +- .../src/Auth/Types/TokenResponse.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Auth/AuthClient.php | 6 +- .../src/Auth/Requests/GetTokenRequest.php | 4 +- .../src/Auth/Types/TokenResponse.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Auth/AuthClient.php | 6 +- .../src/Auth/Requests/GetTokenRequest.php | 4 +- .../src/Auth/Requests/RefreshTokenRequest.php | 4 +- .../src/Auth/Types/TokenResponse.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/object/src/Core/ArrayType.php | 16 -- .../object/src/Core/BaseApiRequest.php | 22 -- .../object/src/Core/Client/BaseApiRequest.php | 22 ++ .../object/src/Core/Client/HttpMethod.php | 12 ++ .../object/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/object/src/Core/Constant.php | 12 -- seed/php-sdk/object/src/Core/DateType.php | 16 -- seed/php-sdk/object/src/Core/HttpMethod.php | 12 -- .../object/src/Core/Json/JsonApiRequest.php | 28 +++ .../object/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../object/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../object/src/Core/Json/JsonEncoder.php | 20 ++ .../object/src/Core/Json/JsonProperty.php | 13 ++ .../object/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../object/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/object/src/Core/Json/Utils.php | 61 ++++++ .../object/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/object/src/Core/JsonDecoder.php | 160 -------------- .../object/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/object/src/Core/JsonEncoder.php | 20 -- seed/php-sdk/object/src/Core/JsonProperty.php | 13 -- .../object/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/object/src/Core/RawClient.php | 138 ------------ .../object/src/Core/SerializableType.php | 179 --------------- .../object/src/Core/Types/ArrayType.php | 16 ++ .../object/src/Core/Types/Constant.php | 12 ++ .../object/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/object/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/object/src/Core/Union.php | 62 ------ seed/php-sdk/object/src/Core/Utils.php | 61 ------ seed/php-sdk/object/src/Types/Name.php | 4 +- seed/php-sdk/object/src/Types/Type.php | 10 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../object/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../object/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../object/tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../object/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Commons/Metadata/Types/Metadata.php | 6 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../objects-with-imports/src/Core/Union.php | 62 ------ .../objects-with-imports/src/Core/Utils.php | 61 ------ .../src/File/Directory/Types/Directory.php | 6 +- .../src/File/Types/File.php | 4 +- .../objects-with-imports/src/Types/Node.php | 4 +- .../objects-with-imports/src/Types/Tree.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/optional/src/Core/ArrayType.php | 16 -- .../optional/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../optional/src/Core/Client/HttpMethod.php | 12 ++ .../optional/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/optional/src/Core/Constant.php | 12 -- seed/php-sdk/optional/src/Core/DateType.php | 16 -- seed/php-sdk/optional/src/Core/HttpMethod.php | 12 -- .../optional/src/Core/Json/JsonApiRequest.php | 28 +++ .../optional/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../optional/src/Core/Json/JsonEncoder.php | 20 ++ .../optional/src/Core/Json/JsonProperty.php | 13 ++ .../optional/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/optional/src/Core/Json/Utils.php | 61 ++++++ .../optional/src/Core/JsonApiRequest.php | 25 --- .../php-sdk/optional/src/Core/JsonDecoder.php | 160 -------------- .../optional/src/Core/JsonDeserializer.php | 202 ----------------- .../php-sdk/optional/src/Core/JsonEncoder.php | 20 -- .../optional/src/Core/JsonProperty.php | 13 -- .../optional/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/optional/src/Core/RawClient.php | 138 ------------ .../optional/src/Core/SerializableType.php | 179 --------------- .../optional/src/Core/Types/ArrayType.php | 16 ++ .../optional/src/Core/Types/Constant.php | 12 ++ .../optional/src/Core/Types/DateType.php | 16 ++ .../php-sdk/optional/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/optional/src/Core/Union.php | 62 ------ seed/php-sdk/optional/src/Core/Utils.php | 61 ------ .../optional/src/Optional/OptionalClient.php | 10 +- seed/php-sdk/optional/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../optional/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../optional/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../package-yml/src/Core/ArrayType.php | 16 -- .../package-yml/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../package-yml/src/Core/Client/RawClient.php | 139 ++++++++++++ .../php-sdk/package-yml/src/Core/Constant.php | 12 -- .../php-sdk/package-yml/src/Core/DateType.php | 16 -- .../package-yml/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../package-yml/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../package-yml/src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../package-yml/src/Core/Json/Utils.php | 61 ++++++ .../package-yml/src/Core/JsonApiRequest.php | 25 --- .../package-yml/src/Core/JsonDecoder.php | 160 -------------- .../package-yml/src/Core/JsonDeserializer.php | 202 ----------------- .../package-yml/src/Core/JsonEncoder.php | 20 -- .../package-yml/src/Core/JsonProperty.php | 13 -- .../package-yml/src/Core/JsonSerializer.php | 190 ---------------- .../package-yml/src/Core/RawClient.php | 138 ------------ .../package-yml/src/Core/SerializableType.php | 179 --------------- .../package-yml/src/Core/Types/ArrayType.php | 16 ++ .../package-yml/src/Core/Types/Constant.php | 12 ++ .../package-yml/src/Core/Types/DateType.php | 16 ++ .../package-yml/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/package-yml/src/Core/Union.php | 62 ------ seed/php-sdk/package-yml/src/Core/Utils.php | 61 ------ seed/php-sdk/package-yml/src/SeedClient.php | 8 +- .../package-yml/src/Service/ServiceClient.php | 6 +- .../package-yml/src/Types/EchoRequest.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../package-yml/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../php-sdk/pagination/src/Core/ArrayType.php | 16 -- .../pagination/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../pagination/src/Core/Client/HttpMethod.php | 12 ++ .../pagination/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/pagination/src/Core/Constant.php | 12 -- seed/php-sdk/pagination/src/Core/DateType.php | 16 -- .../pagination/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../pagination/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../pagination/src/Core/Json/JsonEncoder.php | 20 ++ .../pagination/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../pagination/src/Core/Json/Utils.php | 61 ++++++ .../pagination/src/Core/JsonApiRequest.php | 25 --- .../pagination/src/Core/JsonDecoder.php | 160 -------------- .../pagination/src/Core/JsonDeserializer.php | 202 ----------------- .../pagination/src/Core/JsonEncoder.php | 20 -- .../pagination/src/Core/JsonProperty.php | 13 -- .../pagination/src/Core/JsonSerializer.php | 190 ---------------- .../php-sdk/pagination/src/Core/RawClient.php | 138 ------------ .../pagination/src/Core/SerializableType.php | 179 --------------- .../pagination/src/Core/Types/ArrayType.php | 16 ++ .../pagination/src/Core/Types/Constant.php | 12 ++ .../pagination/src/Core/Types/DateType.php | 16 ++ .../pagination/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/pagination/src/Core/Union.php | 62 ------ seed/php-sdk/pagination/src/Core/Utils.php | 61 ------ seed/php-sdk/pagination/src/SeedClient.php | 2 +- .../pagination/src/Types/UsernameCursor.php | 4 +- .../pagination/src/Types/UsernamePage.php | 6 +- .../Users/Requests/ListUsernamesRequest.php | 2 +- .../ListUsersBodyCursorPaginationRequest.php | 4 +- .../ListUsersBodyOffsetPaginationRequest.php | 4 +- .../ListUsersCursorPaginationRequest.php | 2 +- .../Requests/ListUsersExtendedRequest.php | 2 +- ...istUsersExtendedRequestForOptionalData.php | 2 +- .../ListUsersOffsetPaginationRequest.php | 2 +- .../ListUsersOffsetStepPaginationRequest.php | 2 +- .../Requests/ListWithGlobalConfigRequest.php | 2 +- ...WithOffsetPaginationHasNextPageRequest.php | 2 +- .../ListUsersExtendedOptionalListResponse.php | 4 +- .../Users/Types/ListUsersExtendedResponse.php | 4 +- .../Types/ListUsersPaginationResponse.php | 6 +- .../pagination/src/Users/Types/NextPage.php | 4 +- .../pagination/src/Users/Types/Page.php | 4 +- .../pagination/src/Users/Types/User.php | 4 +- .../src/Users/Types/UserListContainer.php | 6 +- .../Users/Types/UserOptionalListContainer.php | 6 +- .../src/Users/Types/UserOptionalListPage.php | 4 +- .../pagination/src/Users/Types/UserPage.php | 4 +- .../src/Users/Types/UsernameContainer.php | 6 +- .../pagination/src/Users/Types/WithCursor.php | 4 +- .../pagination/src/Users/Types/WithPage.php | 4 +- .../pagination/src/Users/UsersClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../pagination/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../php-sdk/plain-text/src/Core/ArrayType.php | 16 -- .../plain-text/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../plain-text/src/Core/Client/HttpMethod.php | 12 ++ .../plain-text/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/plain-text/src/Core/Constant.php | 12 -- seed/php-sdk/plain-text/src/Core/DateType.php | 16 -- .../plain-text/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../plain-text/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../plain-text/src/Core/Json/JsonEncoder.php | 20 ++ .../plain-text/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../plain-text/src/Core/Json/Utils.php | 61 ++++++ .../plain-text/src/Core/JsonApiRequest.php | 25 --- .../plain-text/src/Core/JsonDecoder.php | 160 -------------- .../plain-text/src/Core/JsonDeserializer.php | 202 ----------------- .../plain-text/src/Core/JsonEncoder.php | 20 -- .../plain-text/src/Core/JsonProperty.php | 13 -- .../plain-text/src/Core/JsonSerializer.php | 190 ---------------- .../php-sdk/plain-text/src/Core/RawClient.php | 138 ------------ .../plain-text/src/Core/SerializableType.php | 179 --------------- .../plain-text/src/Core/Types/ArrayType.php | 16 ++ .../plain-text/src/Core/Types/Constant.php | 12 ++ .../plain-text/src/Core/Types/DateType.php | 16 ++ .../plain-text/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/plain-text/src/Core/Union.php | 62 ------ seed/php-sdk/plain-text/src/Core/Utils.php | 61 ------ seed/php-sdk/plain-text/src/SeedClient.php | 2 +- .../plain-text/src/Service/ServiceClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../plain-text/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../query-parameters/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../query-parameters/src/Core/Constant.php | 12 -- .../query-parameters/src/Core/DateType.php | 16 -- .../query-parameters/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../query-parameters/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../query-parameters/src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../query-parameters/src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../query-parameters/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../query-parameters/src/Core/Types/Union.php | 62 ++++++ .../query-parameters/src/Core/Union.php | 62 ------ .../query-parameters/src/Core/Utils.php | 61 ------ .../query-parameters/src/SeedClient.php | 2 +- .../src/User/Requests/GetUsersRequest.php | 2 +- .../src/User/Types/NestedUser.php | 4 +- .../query-parameters/src/User/Types/User.php | 6 +- .../query-parameters/src/User/UserClient.php | 8 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../reserved-keywords/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../reserved-keywords/src/Core/Constant.php | 12 -- .../reserved-keywords/src/Core/DateType.php | 16 -- .../reserved-keywords/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../reserved-keywords/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../reserved-keywords/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../reserved-keywords/src/Core/Union.php | 62 ------ .../reserved-keywords/src/Core/Utils.php | 61 ------ .../src/Package/PackageClient.php | 6 +- .../src/Package/Requests/TestRequest.php | 2 +- .../src/Package/Types/Package.php | 4 +- .../src/Package/Types/Record.php | 6 +- .../reserved-keywords/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../response-property/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../response-property/src/Core/Constant.php | 12 -- .../response-property/src/Core/DateType.php | 16 -- .../response-property/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../response-property/src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../response-property/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../response-property/src/Core/Union.php | 62 ------ .../response-property/src/Core/Utils.php | 61 ------ .../response-property/src/SeedClient.php | 2 +- .../src/Service/ServiceClient.php | 6 +- .../src/Service/Types/Movie.php | 4 +- .../src/Service/Types/Response.php | 4 +- .../src/Service/Types/WithDocs.php | 4 +- .../src/Types/StringResponse.php | 4 +- .../src/Types/WithMetadata.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Completions/CompletionsClient.php | 6 +- .../Requests/StreamCompletionRequest.php | 4 +- .../Completions/Types/StreamedCompletion.php | 4 +- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Completions/CompletionsClient.php | 6 +- .../Requests/StreamCompletionRequest.php | 4 +- .../Completions/Types/StreamedCompletion.php | 4 +- .../server-sent-events/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../server-sent-events/src/Core/Constant.php | 12 -- .../server-sent-events/src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../server-sent-events/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../server-sent-events/src/Core/Union.php | 62 ------ .../server-sent-events/src/Core/Utils.php | 61 ------ .../server-sent-events/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../simple-fhir/src/Core/ArrayType.php | 16 -- .../simple-fhir/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../simple-fhir/src/Core/Client/RawClient.php | 139 ++++++++++++ .../php-sdk/simple-fhir/src/Core/Constant.php | 12 -- .../php-sdk/simple-fhir/src/Core/DateType.php | 16 -- .../simple-fhir/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../simple-fhir/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../simple-fhir/src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../simple-fhir/src/Core/Json/Utils.php | 61 ++++++ .../simple-fhir/src/Core/JsonApiRequest.php | 25 --- .../simple-fhir/src/Core/JsonDecoder.php | 160 -------------- .../simple-fhir/src/Core/JsonDeserializer.php | 202 ----------------- .../simple-fhir/src/Core/JsonEncoder.php | 20 -- .../simple-fhir/src/Core/JsonProperty.php | 13 -- .../simple-fhir/src/Core/JsonSerializer.php | 190 ---------------- .../simple-fhir/src/Core/RawClient.php | 138 ------------ .../simple-fhir/src/Core/SerializableType.php | 179 --------------- .../simple-fhir/src/Core/Types/ArrayType.php | 16 ++ .../simple-fhir/src/Core/Types/Constant.php | 12 ++ .../simple-fhir/src/Core/Types/DateType.php | 16 ++ .../simple-fhir/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/simple-fhir/src/Core/Union.php | 62 ------ seed/php-sdk/simple-fhir/src/Core/Utils.php | 61 ------ seed/php-sdk/simple-fhir/src/SeedClient.php | 6 +- .../php-sdk/simple-fhir/src/Types/Account.php | 4 +- .../simple-fhir/src/Types/BaseResource.php | 8 +- seed/php-sdk/simple-fhir/src/Types/Memo.php | 4 +- .../php-sdk/simple-fhir/src/Types/Patient.php | 6 +- .../simple-fhir/src/Types/Practitioner.php | 4 +- seed/php-sdk/simple-fhir/src/Types/Script.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../simple-fhir/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/Dummy/DummyClient.php | 8 +- .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../src/Core/Union.php | 62 ------ .../src/Core/Utils.php | 61 ------ .../src/Dummy/DummyClient.php | 8 +- .../src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../streaming-parameter/src/Core/Constant.php | 12 -- .../streaming-parameter/src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../streaming-parameter/src/Core/Union.php | 62 ------ .../streaming-parameter/src/Core/Utils.php | 61 ------ .../src/Dummy/DummyClient.php | 6 +- .../src/Dummy/Requests/GenerateRequest.php | 4 +- .../src/Dummy/Types/RegularResponse.php | 4 +- .../src/Dummy/Types/StreamResponse.php | 4 +- .../streaming-parameter/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/streaming/src/Core/ArrayType.php | 16 -- .../streaming/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../streaming/src/Core/Client/HttpMethod.php | 12 ++ .../streaming/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/streaming/src/Core/Constant.php | 12 -- seed/php-sdk/streaming/src/Core/DateType.php | 16 -- .../php-sdk/streaming/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../streaming/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../streaming/src/Core/Json/JsonEncoder.php | 20 ++ .../streaming/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-sdk/streaming/src/Core/Json/Utils.php | 61 ++++++ .../streaming/src/Core/JsonApiRequest.php | 25 --- .../streaming/src/Core/JsonDecoder.php | 160 -------------- .../streaming/src/Core/JsonDeserializer.php | 202 ----------------- .../streaming/src/Core/JsonEncoder.php | 20 -- .../streaming/src/Core/JsonProperty.php | 13 -- .../streaming/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/streaming/src/Core/RawClient.php | 138 ------------ .../streaming/src/Core/SerializableType.php | 179 --------------- .../streaming/src/Core/Types/ArrayType.php | 16 ++ .../streaming/src/Core/Types/Constant.php | 12 ++ .../streaming/src/Core/Types/DateType.php | 16 ++ .../streaming/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/streaming/src/Core/Union.php | 62 ------ seed/php-sdk/streaming/src/Core/Utils.php | 61 ------ .../streaming/src/Dummy/DummyClient.php | 6 +- .../Dummy/Requests/GenerateStreamRequest.php | 4 +- .../src/Dummy/Requests/Generateequest.php | 4 +- .../src/Dummy/Types/StreamResponse.php | 4 +- seed/php-sdk/streaming/src/SeedClient.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../streaming/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/trace/src/Admin/AdminClient.php | 8 +- .../Requests/StoreTracedTestCaseRequest.php | 6 +- .../Requests/StoreTracedWorkspaceRequest.php | 6 +- .../Types/BinaryTreeNodeAndTreeValue.php | 4 +- .../src/Commons/Types/BinaryTreeNodeValue.php | 4 +- .../src/Commons/Types/BinaryTreeValue.php | 6 +- .../src/Commons/Types/DebugKeyValuePairs.php | 4 +- .../trace/src/Commons/Types/DebugMapValue.php | 6 +- .../DoublyLinkedListNodeAndListValue.php | 4 +- .../Types/DoublyLinkedListNodeValue.php | 4 +- .../Commons/Types/DoublyLinkedListValue.php | 6 +- .../trace/src/Commons/Types/FileInfo.php | 4 +- .../trace/src/Commons/Types/GenericValue.php | 4 +- .../trace/src/Commons/Types/KeyValuePair.php | 4 +- .../trace/src/Commons/Types/ListType.php | 4 +- .../trace/src/Commons/Types/MapType.php | 4 +- .../trace/src/Commons/Types/MapValue.php | 6 +- .../SinglyLinkedListNodeAndListValue.php | 4 +- .../Types/SinglyLinkedListNodeValue.php | 4 +- .../Commons/Types/SinglyLinkedListValue.php | 6 +- .../trace/src/Commons/Types/TestCase.php | 6 +- .../Types/TestCaseWithExpectedResult.php | 4 +- seed/php-sdk/trace/src/Core/ArrayType.php | 16 -- .../php-sdk/trace/src/Core/BaseApiRequest.php | 22 -- .../trace/src/Core/Client/BaseApiRequest.php | 22 ++ .../trace/src/Core/Client/HttpMethod.php | 12 ++ .../trace/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/trace/src/Core/Constant.php | 12 -- seed/php-sdk/trace/src/Core/DateType.php | 16 -- seed/php-sdk/trace/src/Core/HttpMethod.php | 12 -- .../trace/src/Core/Json/JsonApiRequest.php | 28 +++ .../trace/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../trace/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../trace/src/Core/Json/JsonEncoder.php | 20 ++ .../trace/src/Core/Json/JsonProperty.php | 13 ++ .../trace/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../trace/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/trace/src/Core/Json/Utils.php | 61 ++++++ .../php-sdk/trace/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/trace/src/Core/JsonDecoder.php | 160 -------------- .../trace/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/trace/src/Core/JsonEncoder.php | 20 -- seed/php-sdk/trace/src/Core/JsonProperty.php | 13 -- .../php-sdk/trace/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/trace/src/Core/RawClient.php | 138 ------------ .../trace/src/Core/SerializableType.php | 179 --------------- .../trace/src/Core/Types/ArrayType.php | 16 ++ .../php-sdk/trace/src/Core/Types/Constant.php | 12 ++ .../php-sdk/trace/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/trace/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/trace/src/Core/Union.php | 62 ------ seed/php-sdk/trace/src/Core/Utils.php | 61 ------ .../trace/src/Homepage/HomepageClient.php | 10 +- .../LangServer/Types/LangServerRequest.php | 4 +- .../LangServer/Types/LangServerResponse.php | 4 +- .../trace/src/Migration/MigrationClient.php | 8 +- .../GetAttemptedMigrationsRequest.php | 2 +- .../trace/src/Migration/Types/Migration.php | 4 +- .../trace/src/Playlist/PlaylistClient.php | 10 +- .../Requests/CreatePlaylistRequest.php | 2 +- .../Playlist/Requests/GetPlaylistsRequest.php | 2 +- .../trace/src/Playlist/Types/Playlist.php | 4 +- .../Playlist/Types/PlaylistCreateRequest.php | 6 +- .../Playlist/Types/UpdatePlaylistRequest.php | 6 +- .../trace/src/Problem/ProblemClient.php | 8 +- .../GetDefaultStarterFilesRequest.php | 6 +- .../Problem/Types/CreateProblemRequest.php | 6 +- .../Types/GenericCreateProblemError.php | 4 +- .../Types/GetDefaultStarterFilesResponse.php | 6 +- .../src/Problem/Types/ProblemDescription.php | 6 +- .../trace/src/Problem/Types/ProblemFiles.php | 6 +- .../trace/src/Problem/Types/ProblemInfo.php | 6 +- .../Problem/Types/UpdateProblemResponse.php | 4 +- .../src/Problem/Types/VariableTypeAndName.php | 4 +- seed/php-sdk/trace/src/SeedClient.php | 2 +- .../trace/src/Submission/SubmissionClient.php | 6 +- .../Types/BuildingExecutorResponse.php | 4 +- .../src/Submission/Types/CompileError.php | 4 +- .../Types/CustomTestCasesUnsupported.php | 4 +- .../src/Submission/Types/ErroredResponse.php | 4 +- .../src/Submission/Types/ExceptionInfo.php | 4 +- .../Types/ExecutionSessionResponse.php | 4 +- .../Types/ExecutionSessionState.php | 4 +- .../Types/ExistingSubmissionExecuting.php | 4 +- .../Submission/Types/ExpressionLocation.php | 4 +- .../src/Submission/Types/FinishedResponse.php | 4 +- .../GetExecutionSessionStateResponse.php | 6 +- .../Types/GetSubmissionStateResponse.php | 6 +- .../Types/GetTraceResponsesPageRequest.php | 4 +- .../src/Submission/Types/GradedResponse.php | 6 +- .../src/Submission/Types/GradedResponseV2.php | 6 +- .../Submission/Types/GradedTestCaseUpdate.php | 4 +- .../Types/InitializeProblemRequest.php | 4 +- .../src/Submission/Types/InternalError.php | 4 +- .../Types/InvalidRequestResponse.php | 4 +- .../LightweightStackframeInformation.php | 4 +- .../Types/RecordedResponseNotification.php | 4 +- .../Types/RecordedTestCaseUpdate.php | 4 +- .../Types/RecordingResponseNotification.php | 4 +- .../src/Submission/Types/RunningResponse.php | 4 +- .../src/Submission/Types/RuntimeError.php | 4 +- .../trace/src/Submission/Types/Scope.php | 6 +- .../trace/src/Submission/Types/StackFrame.php | 6 +- .../src/Submission/Types/StackInformation.php | 4 +- .../src/Submission/Types/StderrResponse.php | 4 +- .../src/Submission/Types/StdoutResponse.php | 4 +- .../src/Submission/Types/StopRequest.php | 4 +- .../src/Submission/Types/StoppedResponse.php | 4 +- .../Submission/Types/SubmissionFileInfo.php | 4 +- .../Submission/Types/SubmissionIdNotFound.php | 4 +- .../src/Submission/Types/SubmitRequestV2.php | 6 +- .../Submission/Types/TerminatedResponse.php | 2 +- .../Submission/Types/TestCaseHiddenGrade.php | 4 +- .../Types/TestCaseNonHiddenGrade.php | 4 +- .../src/Submission/Types/TestCaseResult.php | 4 +- .../Types/TestCaseResultWithStdout.php | 4 +- .../Submission/Types/TestSubmissionState.php | 6 +- .../Types/TestSubmissionStatusV2.php | 6 +- .../Submission/Types/TestSubmissionUpdate.php | 6 +- .../src/Submission/Types/TraceResponse.php | 4 +- .../src/Submission/Types/TraceResponseV2.php | 4 +- .../Submission/Types/TraceResponsesPage.php | 6 +- .../Submission/Types/TraceResponsesPageV2.php | 6 +- .../trace/src/Submission/Types/TracedFile.php | 4 +- .../src/Submission/Types/TracedTestCase.php | 4 +- .../Types/UnexpectedLanguageError.php | 4 +- .../src/Submission/Types/WorkspaceFiles.php | 6 +- .../Submission/Types/WorkspaceRanResponse.php | 4 +- .../Submission/Types/WorkspaceRunDetails.php | 4 +- .../Types/WorkspaceStarterFilesResponse.php | 6 +- .../Types/WorkspaceStarterFilesResponseV2.php | 6 +- .../Types/WorkspaceSubmissionState.php | 4 +- .../Types/WorkspaceSubmissionStatusV2.php | 6 +- .../Types/WorkspaceSubmissionUpdate.php | 6 +- .../Types/WorkspaceSubmitRequest.php | 6 +- .../Types/WorkspaceTracedUpdate.php | 4 +- .../trace/src/Sysprop/SyspropClient.php | 8 +- .../trace/src/V2/Problem/ProblemClient.php | 8 +- .../src/V2/Problem/Types/BasicCustomFiles.php | 6 +- .../Problem/Types/BasicTestCaseTemplate.php | 4 +- .../Problem/Types/CreateProblemRequestV2.php | 6 +- .../Types/DeepEqualityCorrectnessCheck.php | 4 +- .../V2/Problem/Types/DefaultProvidedFile.php | 6 +- .../trace/src/V2/Problem/Types/FileInfoV2.php | 4 +- .../trace/src/V2/Problem/Types/Files.php | 6 +- .../Problem/Types/FunctionImplementation.php | 4 +- ...tionImplementationForMultipleLanguages.php | 6 +- .../src/V2/Problem/Types/GeneratedFiles.php | 6 +- .../Types/GetBasicSolutionFileRequest.php | 4 +- .../Types/GetBasicSolutionFileResponse.php | 6 +- .../Types/GetFunctionSignatureRequest.php | 4 +- .../Types/GetFunctionSignatureResponse.php | 6 +- .../Types/GetGeneratedTestCaseFileRequest.php | 4 +- ...etGeneratedTestCaseTemplateFileRequest.php | 4 +- .../Types/LightweightProblemInfoV2.php | 6 +- .../Types/NonVoidFunctionDefinition.php | 4 +- .../Types/NonVoidFunctionSignature.php | 6 +- .../trace/src/V2/Problem/Types/Parameter.php | 4 +- .../src/V2/Problem/Types/ProblemInfoV2.php | 6 +- .../src/V2/Problem/Types/TestCaseExpects.php | 4 +- .../Problem/Types/TestCaseImplementation.php | 4 +- .../TestCaseImplementationDescription.php | 6 +- .../src/V2/Problem/Types/TestCaseMetadata.php | 4 +- .../src/V2/Problem/Types/TestCaseTemplate.php | 4 +- .../trace/src/V2/Problem/Types/TestCaseV2.php | 6 +- ...TestCaseWithActualResultImplementation.php | 4 +- .../Problem/Types/VoidFunctionDefinition.php | 6 +- ...unctionDefinitionThatTakesActualResult.php | 6 +- .../Problem/Types/VoidFunctionSignature.php | 6 +- ...FunctionSignatureThatTakesActualResult.php | 6 +- seed/php-sdk/trace/src/V2/V2Client.php | 6 +- .../trace/src/V2/V3/Problem/ProblemClient.php | 8 +- .../V2/V3/Problem/Types/BasicCustomFiles.php | 6 +- .../Problem/Types/BasicTestCaseTemplate.php | 4 +- .../Problem/Types/CreateProblemRequestV2.php | 6 +- .../Types/DeepEqualityCorrectnessCheck.php | 4 +- .../V3/Problem/Types/DefaultProvidedFile.php | 6 +- .../src/V2/V3/Problem/Types/FileInfoV2.php | 4 +- .../trace/src/V2/V3/Problem/Types/Files.php | 6 +- .../Problem/Types/FunctionImplementation.php | 4 +- ...tionImplementationForMultipleLanguages.php | 6 +- .../V2/V3/Problem/Types/GeneratedFiles.php | 6 +- .../Types/GetBasicSolutionFileRequest.php | 4 +- .../Types/GetBasicSolutionFileResponse.php | 6 +- .../Types/GetFunctionSignatureRequest.php | 4 +- .../Types/GetFunctionSignatureResponse.php | 6 +- .../Types/GetGeneratedTestCaseFileRequest.php | 4 +- ...etGeneratedTestCaseTemplateFileRequest.php | 4 +- .../Types/LightweightProblemInfoV2.php | 6 +- .../Types/NonVoidFunctionDefinition.php | 4 +- .../Types/NonVoidFunctionSignature.php | 6 +- .../src/V2/V3/Problem/Types/Parameter.php | 4 +- .../src/V2/V3/Problem/Types/ProblemInfoV2.php | 6 +- .../V2/V3/Problem/Types/TestCaseExpects.php | 4 +- .../Problem/Types/TestCaseImplementation.php | 4 +- .../TestCaseImplementationDescription.php | 6 +- .../V2/V3/Problem/Types/TestCaseMetadata.php | 4 +- .../V2/V3/Problem/Types/TestCaseTemplate.php | 4 +- .../src/V2/V3/Problem/Types/TestCaseV2.php | 6 +- ...TestCaseWithActualResultImplementation.php | 4 +- .../Problem/Types/VoidFunctionDefinition.php | 6 +- ...unctionDefinitionThatTakesActualResult.php | 6 +- .../Problem/Types/VoidFunctionSignature.php | 6 +- ...FunctionSignatureThatTakesActualResult.php | 6 +- seed/php-sdk/trace/src/V2/V3/V3Client.php | 2 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../trace/tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../trace/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../trace/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../trace/tests/Seed/Core/RawClientTest.php | 101 --------- .../trace/tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../trace/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../src/Core/Constant.php | 12 -- .../src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../undiscriminated-unions/src/Core/Union.php | 62 ------ .../undiscriminated-unions/src/Core/Utils.php | 61 ------ .../undiscriminated-unions/src/SeedClient.php | 2 +- .../src/Union/UnionClient.php | 12 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/unions/src/Core/ArrayType.php | 16 -- .../unions/src/Core/BaseApiRequest.php | 22 -- .../unions/src/Core/Client/BaseApiRequest.php | 22 ++ .../unions/src/Core/Client/HttpMethod.php | 12 ++ .../unions/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/unions/src/Core/Constant.php | 12 -- seed/php-sdk/unions/src/Core/DateType.php | 16 -- seed/php-sdk/unions/src/Core/HttpMethod.php | 12 -- .../unions/src/Core/Json/JsonApiRequest.php | 28 +++ .../unions/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../unions/src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../unions/src/Core/Json/JsonEncoder.php | 20 ++ .../unions/src/Core/Json/JsonProperty.php | 13 ++ .../unions/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../unions/src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/unions/src/Core/Json/Utils.php | 61 ++++++ .../unions/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/unions/src/Core/JsonDecoder.php | 160 -------------- .../unions/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/unions/src/Core/JsonEncoder.php | 20 -- seed/php-sdk/unions/src/Core/JsonProperty.php | 13 -- .../unions/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/unions/src/Core/RawClient.php | 138 ------------ .../unions/src/Core/SerializableType.php | 179 --------------- .../unions/src/Core/Types/ArrayType.php | 16 ++ .../unions/src/Core/Types/Constant.php | 12 ++ .../unions/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/unions/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/unions/src/Core/Union.php | 62 ------ seed/php-sdk/unions/src/Core/Utils.php | 61 ------ seed/php-sdk/unions/src/SeedClient.php | 2 +- seed/php-sdk/unions/src/Types/Types/Bar.php | 4 +- seed/php-sdk/unions/src/Types/Types/Foo.php | 4 +- .../php-sdk/unions/src/Union/Types/Circle.php | 4 +- .../src/Union/Types/GetShapeRequest.php | 4 +- .../php-sdk/unions/src/Union/Types/Square.php | 4 +- seed/php-sdk/unions/src/Union/UnionClient.php | 8 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../unions/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../unions/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../unions/tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../unions/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/unknown/src/Core/ArrayType.php | 16 -- .../unknown/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../unknown/src/Core/Client/HttpMethod.php | 12 ++ .../unknown/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/unknown/src/Core/Constant.php | 12 -- seed/php-sdk/unknown/src/Core/DateType.php | 16 -- seed/php-sdk/unknown/src/Core/HttpMethod.php | 12 -- .../unknown/src/Core/Json/JsonApiRequest.php | 28 +++ .../unknown/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../unknown/src/Core/Json/JsonEncoder.php | 20 ++ .../unknown/src/Core/Json/JsonProperty.php | 13 ++ .../unknown/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/unknown/src/Core/Json/Utils.php | 61 ++++++ .../unknown/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/unknown/src/Core/JsonDecoder.php | 160 -------------- .../unknown/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/unknown/src/Core/JsonEncoder.php | 20 -- .../php-sdk/unknown/src/Core/JsonProperty.php | 13 -- .../unknown/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/unknown/src/Core/RawClient.php | 138 ------------ .../unknown/src/Core/SerializableType.php | 179 --------------- .../unknown/src/Core/Types/ArrayType.php | 16 ++ .../unknown/src/Core/Types/Constant.php | 12 ++ .../unknown/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/unknown/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/unknown/src/Core/Union.php | 62 ------ seed/php-sdk/unknown/src/Core/Utils.php | 61 ------ seed/php-sdk/unknown/src/SeedClient.php | 2 +- .../unknown/src/Unknown/Types/MyObject.php | 4 +- .../unknown/src/Unknown/UnknownClient.php | 8 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../unknown/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../unknown/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../unknown/tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../unknown/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../php-sdk/validation/src/Core/ArrayType.php | 16 -- .../validation/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../validation/src/Core/Client/HttpMethod.php | 12 ++ .../validation/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/validation/src/Core/Constant.php | 12 -- seed/php-sdk/validation/src/Core/DateType.php | 16 -- .../validation/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../validation/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../validation/src/Core/Json/JsonEncoder.php | 20 ++ .../validation/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../validation/src/Core/Json/Utils.php | 61 ++++++ .../validation/src/Core/JsonApiRequest.php | 25 --- .../validation/src/Core/JsonDecoder.php | 160 -------------- .../validation/src/Core/JsonDeserializer.php | 202 ----------------- .../validation/src/Core/JsonEncoder.php | 20 -- .../validation/src/Core/JsonProperty.php | 13 -- .../validation/src/Core/JsonSerializer.php | 190 ---------------- .../php-sdk/validation/src/Core/RawClient.php | 138 ------------ .../validation/src/Core/SerializableType.php | 179 --------------- .../validation/src/Core/Types/ArrayType.php | 16 ++ .../validation/src/Core/Types/Constant.php | 12 ++ .../validation/src/Core/Types/DateType.php | 16 ++ .../validation/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/validation/src/Core/Union.php | 62 ------ seed/php-sdk/validation/src/Core/Utils.php | 61 ------ .../validation/src/Requests/CreateRequest.php | 4 +- .../validation/src/Requests/GetRequest.php | 2 +- seed/php-sdk/validation/src/SeedClient.php | 6 +- seed/php-sdk/validation/src/Types/Type.php | 4 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../validation/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/variables/src/Core/ArrayType.php | 16 -- .../variables/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../variables/src/Core/Client/HttpMethod.php | 12 ++ .../variables/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/variables/src/Core/Constant.php | 12 -- seed/php-sdk/variables/src/Core/DateType.php | 16 -- .../php-sdk/variables/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../variables/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../variables/src/Core/Json/JsonEncoder.php | 20 ++ .../variables/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-sdk/variables/src/Core/Json/Utils.php | 61 ++++++ .../variables/src/Core/JsonApiRequest.php | 25 --- .../variables/src/Core/JsonDecoder.php | 160 -------------- .../variables/src/Core/JsonDeserializer.php | 202 ----------------- .../variables/src/Core/JsonEncoder.php | 20 -- .../variables/src/Core/JsonProperty.php | 13 -- .../variables/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/variables/src/Core/RawClient.php | 138 ------------ .../variables/src/Core/SerializableType.php | 179 --------------- .../variables/src/Core/Types/ArrayType.php | 16 ++ .../variables/src/Core/Types/Constant.php | 12 ++ .../variables/src/Core/Types/DateType.php | 16 ++ .../variables/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/variables/src/Core/Union.php | 62 ------ seed/php-sdk/variables/src/Core/Utils.php | 61 ------ seed/php-sdk/variables/src/SeedClient.php | 2 +- .../variables/src/Service/ServiceClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../variables/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- .../version-no-default/src/Core/ArrayType.php | 16 -- .../src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../src/Core/Client/HttpMethod.php | 12 ++ .../src/Core/Client/RawClient.php | 139 ++++++++++++ .../version-no-default/src/Core/Constant.php | 12 -- .../version-no-default/src/Core/DateType.php | 16 -- .../src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../src/Core/Json/JsonEncoder.php | 20 ++ .../src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../src/Core/Json/Utils.php | 61 ++++++ .../src/Core/JsonApiRequest.php | 25 --- .../src/Core/JsonDecoder.php | 160 -------------- .../src/Core/JsonDeserializer.php | 202 ----------------- .../src/Core/JsonEncoder.php | 20 -- .../src/Core/JsonProperty.php | 13 -- .../src/Core/JsonSerializer.php | 190 ---------------- .../version-no-default/src/Core/RawClient.php | 138 ------------ .../src/Core/SerializableType.php | 179 --------------- .../src/Core/Types/ArrayType.php | 16 ++ .../src/Core/Types/Constant.php | 12 ++ .../src/Core/Types/DateType.php | 16 ++ .../src/Core/Types/Union.php | 62 ++++++ .../version-no-default/src/Core/Union.php | 62 ------ .../version-no-default/src/Core/Utils.php | 61 ------ .../version-no-default/src/SeedClient.php | 2 +- .../src/User/Types/User.php | 4 +- .../src/User/UserClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/version/src/Core/ArrayType.php | 16 -- .../version/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../version/src/Core/Client/HttpMethod.php | 12 ++ .../version/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/version/src/Core/Constant.php | 12 -- seed/php-sdk/version/src/Core/DateType.php | 16 -- seed/php-sdk/version/src/Core/HttpMethod.php | 12 -- .../version/src/Core/Json/JsonApiRequest.php | 28 +++ .../version/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../version/src/Core/Json/JsonEncoder.php | 20 ++ .../version/src/Core/Json/JsonProperty.php | 13 ++ .../version/src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ seed/php-sdk/version/src/Core/Json/Utils.php | 61 ++++++ .../version/src/Core/JsonApiRequest.php | 25 --- seed/php-sdk/version/src/Core/JsonDecoder.php | 160 -------------- .../version/src/Core/JsonDeserializer.php | 202 ----------------- seed/php-sdk/version/src/Core/JsonEncoder.php | 20 -- .../php-sdk/version/src/Core/JsonProperty.php | 13 -- .../version/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/version/src/Core/RawClient.php | 138 ------------ .../version/src/Core/SerializableType.php | 179 --------------- .../version/src/Core/Types/ArrayType.php | 16 ++ .../version/src/Core/Types/Constant.php | 12 ++ .../version/src/Core/Types/DateType.php | 16 ++ seed/php-sdk/version/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/version/src/Core/Union.php | 62 ------ seed/php-sdk/version/src/Core/Utils.php | 61 ------ seed/php-sdk/version/src/SeedClient.php | 2 +- seed/php-sdk/version/src/User/Types/User.php | 4 +- seed/php-sdk/version/src/User/UserClient.php | 6 +- .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../version/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../version/tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../version/tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../version/tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- seed/php-sdk/websocket/src/Core/ArrayType.php | 16 -- .../websocket/src/Core/BaseApiRequest.php | 22 -- .../src/Core/Client/BaseApiRequest.php | 22 ++ .../websocket/src/Core/Client/HttpMethod.php | 12 ++ .../websocket/src/Core/Client/RawClient.php | 139 ++++++++++++ seed/php-sdk/websocket/src/Core/Constant.php | 12 -- seed/php-sdk/websocket/src/Core/DateType.php | 16 -- .../php-sdk/websocket/src/Core/HttpMethod.php | 12 -- .../src/Core/Json/JsonApiRequest.php | 28 +++ .../websocket/src/Core/Json/JsonDecoder.php | 161 ++++++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++++++ .../websocket/src/Core/Json/JsonEncoder.php | 20 ++ .../websocket/src/Core/Json/JsonProperty.php | 13 ++ .../src/Core/Json/JsonSerializer.php | 192 +++++++++++++++++ .../src/Core/Json/SerializableType.php | 182 ++++++++++++++++ .../php-sdk/websocket/src/Core/Json/Utils.php | 61 ++++++ .../websocket/src/Core/JsonApiRequest.php | 25 --- .../websocket/src/Core/JsonDecoder.php | 160 -------------- .../websocket/src/Core/JsonDeserializer.php | 202 ----------------- .../websocket/src/Core/JsonEncoder.php | 20 -- .../websocket/src/Core/JsonProperty.php | 13 -- .../websocket/src/Core/JsonSerializer.php | 190 ---------------- seed/php-sdk/websocket/src/Core/RawClient.php | 138 ------------ .../websocket/src/Core/SerializableType.php | 179 --------------- .../websocket/src/Core/Types/ArrayType.php | 16 ++ .../websocket/src/Core/Types/Constant.php | 12 ++ .../websocket/src/Core/Types/DateType.php | 16 ++ .../websocket/src/Core/Types/Union.php | 62 ++++++ seed/php-sdk/websocket/src/Core/Union.php | 62 ------ seed/php-sdk/websocket/src/Core/Utils.php | 61 ------ .../tests/Seed/Core/Client/RawClientTest.php | 101 +++++++++ .../tests/Seed/Core/DateArrayTypeTest.php | 55 ----- .../tests/Seed/Core/EmptyArraysTest.php | 73 ------- .../websocket/tests/Seed/Core/EnumTest.php | 76 ------- .../tests/Seed/Core/InvalidTypesTest.php | 45 ---- .../Seed/Core/Json/DateArrayTypeTest.php | 55 +++++ .../tests/Seed/Core/Json/EmptyArraysTest.php | 73 +++++++ .../tests/Seed/Core/Json/EnumTest.php | 76 +++++++ .../tests/Seed/Core/Json/InvalidTypesTest.php | 45 ++++ .../Seed/Core/Json/MixedDateArrayTypeTest.php | 60 ++++++ .../Core/Json/NestedUnionArrayTypeTest.php | 99 +++++++++ .../Seed/Core/Json/NullPropertyTypeTest.php | 50 +++++ .../Seed/Core/Json/NullableArrayTypeTest.php | 50 +++++ .../tests/Seed/Core/Json/ScalarTypesTest.php | 121 +++++++++++ .../tests/Seed/Core/Json/TestTypeTest.php | 201 +++++++++++++++++ .../Seed/Core/Json/UnionArrayTypeTest.php | 56 +++++ .../Seed/Core/Json/UnionPropertyTypeTest.php | 116 ++++++++++ .../Seed/Core/MixedDateArrayTypeTest.php | 60 ------ .../Seed/Core/NestedUnionArrayTypeTest.php | 99 --------- .../tests/Seed/Core/NullPropertyTypeTest.php | 50 ----- .../tests/Seed/Core/NullableArrayTypeTest.php | 50 ----- .../tests/Seed/Core/RawClientTest.php | 101 --------- .../tests/Seed/Core/ScalarTypesTest.php | 121 ----------- .../tests/Seed/Core/TestTypeTest.php | 201 ----------------- .../tests/Seed/Core/UnionArrayTypeTest.php | 56 ----- .../tests/Seed/Core/UnionPropertyTypeTest.php | 116 ---------- 7195 files changed, 261242 insertions(+), 259939 deletions(-) rename generators/php/codegen/src/asIs/{ => Client}/BaseApiRequest.Template.php (100%) rename generators/php/codegen/src/asIs/{ => Client}/HttpMethod.Template.php (100%) rename generators/php/codegen/src/asIs/{ => Client}/RawClient.Template.php (98%) rename generators/php/codegen/src/asIs/{ => Client}/RawClientTest.Template.php (94%) rename generators/php/codegen/src/asIs/{ => Json}/DateArrayTypeTest.Template.php (92%) rename generators/php/codegen/src/asIs/{ => Json}/EmptyArraysTest.Template.php (92%) rename generators/php/codegen/src/asIs/{ => Json}/EnumTest.Template.php (91%) rename generators/php/codegen/src/asIs/{ => Json}/InvalidTypesTest.Template.php (91%) rename generators/php/codegen/src/asIs/{ => Json}/JsonApiRequest.Template.php (89%) rename generators/php/codegen/src/asIs/{ => Json}/JsonDecoder.Template.php (99%) rename generators/php/codegen/src/asIs/{ => Json}/JsonDeserializer.Template.php (98%) rename generators/php/codegen/src/asIs/{ => Json}/JsonEncoder.Template.php (100%) rename generators/php/codegen/src/asIs/{ => Json}/JsonProperty.Template.php (100%) rename generators/php/codegen/src/asIs/{ => Json}/JsonSerializer.Template.php (98%) rename generators/php/codegen/src/asIs/{ => Json}/MixedDateArrayTypeTest.Template.php (90%) rename generators/php/codegen/src/asIs/{ => Json}/NestedUnionArrayTypeTest.Template.php (93%) rename generators/php/codegen/src/asIs/{ => Json}/NullPropertyTypeTest.Template.php (93%) rename generators/php/codegen/src/asIs/{ => Json}/NullableArrayTypeTest.Template.php (87%) rename generators/php/codegen/src/asIs/{ => Json}/ScalarTypesTest.Template.php (95%) rename generators/php/codegen/src/asIs/{ => Json}/SerializableType.Template.php (97%) rename generators/php/codegen/src/asIs/{ => Json}/TestTypeTest.Template.php (97%) rename generators/php/codegen/src/asIs/{ => Json}/UnionArrayTypeTest.Template.php (89%) rename generators/php/codegen/src/asIs/{ => Json}/UnionPropertyTypeTest.Template.php (96%) rename generators/php/codegen/src/asIs/{ => Json}/Utils.Template.php (98%) rename generators/php/codegen/src/asIs/{ => Types}/ArrayType.Template.php (100%) rename generators/php/codegen/src/asIs/{ => Types}/Constant.Template.php (100%) rename generators/php/codegen/src/asIs/{ => Types}/DateType.Template.php (100%) rename generators/php/codegen/src/asIs/{ => Types}/Union.Template.php (98%) delete mode 100644 seed/php-model/alias-extends/src/Core/ArrayType.php delete mode 100644 seed/php-model/alias-extends/src/Core/Constant.php delete mode 100644 seed/php-model/alias-extends/src/Core/DateType.php create mode 100644 seed/php-model/alias-extends/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/alias-extends/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/alias-extends/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/alias-extends/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/alias-extends/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/alias-extends/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/alias-extends/src/Core/Json/Utils.php delete mode 100644 seed/php-model/alias-extends/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/alias-extends/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/alias-extends/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/alias-extends/src/Core/JsonProperty.php delete mode 100644 seed/php-model/alias-extends/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/alias-extends/src/Core/SerializableType.php create mode 100644 seed/php-model/alias-extends/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/alias-extends/src/Core/Types/Constant.php create mode 100644 seed/php-model/alias-extends/src/Core/Types/DateType.php create mode 100644 seed/php-model/alias-extends/src/Core/Types/Union.php delete mode 100644 seed/php-model/alias-extends/src/Core/Union.php delete mode 100644 seed/php-model/alias-extends/src/Core/Utils.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/alias/src/Core/ArrayType.php delete mode 100644 seed/php-model/alias/src/Core/Constant.php delete mode 100644 seed/php-model/alias/src/Core/DateType.php create mode 100644 seed/php-model/alias/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/alias/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/alias/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/alias/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/alias/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/alias/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/alias/src/Core/Json/Utils.php delete mode 100644 seed/php-model/alias/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/alias/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/alias/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/alias/src/Core/JsonProperty.php delete mode 100644 seed/php-model/alias/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/alias/src/Core/SerializableType.php create mode 100644 seed/php-model/alias/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/alias/src/Core/Types/Constant.php create mode 100644 seed/php-model/alias/src/Core/Types/DateType.php create mode 100644 seed/php-model/alias/src/Core/Types/Union.php delete mode 100644 seed/php-model/alias/src/Core/Union.php delete mode 100644 seed/php-model/alias/src/Core/Utils.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/alias/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/any-auth/src/Core/ArrayType.php delete mode 100644 seed/php-model/any-auth/src/Core/Constant.php delete mode 100644 seed/php-model/any-auth/src/Core/DateType.php create mode 100644 seed/php-model/any-auth/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/any-auth/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/any-auth/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/any-auth/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/any-auth/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/any-auth/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/any-auth/src/Core/Json/Utils.php delete mode 100644 seed/php-model/any-auth/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/any-auth/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/any-auth/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/any-auth/src/Core/JsonProperty.php delete mode 100644 seed/php-model/any-auth/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/any-auth/src/Core/SerializableType.php create mode 100644 seed/php-model/any-auth/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/any-auth/src/Core/Types/Constant.php create mode 100644 seed/php-model/any-auth/src/Core/Types/DateType.php create mode 100644 seed/php-model/any-auth/src/Core/Types/Union.php delete mode 100644 seed/php-model/any-auth/src/Core/Union.php delete mode 100644 seed/php-model/any-auth/src/Core/Utils.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/ArrayType.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/Constant.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/DateType.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Json/Utils.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/JsonProperty.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/SerializableType.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Types/Constant.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Types/DateType.php create mode 100644 seed/php-model/api-wide-base-path/src/Core/Types/Union.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/Union.php delete mode 100644 seed/php-model/api-wide-base-path/src/Core/Utils.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/audiences/src/Core/ArrayType.php delete mode 100644 seed/php-model/audiences/src/Core/Constant.php delete mode 100644 seed/php-model/audiences/src/Core/DateType.php create mode 100644 seed/php-model/audiences/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/audiences/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/audiences/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/audiences/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/audiences/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/audiences/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/audiences/src/Core/Json/Utils.php delete mode 100644 seed/php-model/audiences/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/audiences/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/audiences/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/audiences/src/Core/JsonProperty.php delete mode 100644 seed/php-model/audiences/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/audiences/src/Core/SerializableType.php create mode 100644 seed/php-model/audiences/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/audiences/src/Core/Types/Constant.php create mode 100644 seed/php-model/audiences/src/Core/Types/DateType.php create mode 100644 seed/php-model/audiences/src/Core/Types/Union.php delete mode 100644 seed/php-model/audiences/src/Core/Union.php delete mode 100644 seed/php-model/audiences/src/Core/Utils.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/audiences/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/ArrayType.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/Constant.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/DateType.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Json/Utils.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/JsonProperty.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/SerializableType.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Types/Constant.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Types/DateType.php create mode 100644 seed/php-model/auth-environment-variables/src/Core/Types/Union.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/Union.php delete mode 100644 seed/php-model/auth-environment-variables/src/Core/Utils.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/ArrayType.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Constant.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/DateType.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Json/Utils.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/JsonProperty.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/SerializableType.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Types/Constant.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Types/DateType.php create mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Types/Union.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Union.php delete mode 100644 seed/php-model/basic-auth-environment-variables/src/Core/Utils.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/basic-auth/src/Core/ArrayType.php delete mode 100644 seed/php-model/basic-auth/src/Core/Constant.php delete mode 100644 seed/php-model/basic-auth/src/Core/DateType.php create mode 100644 seed/php-model/basic-auth/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/basic-auth/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/basic-auth/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/basic-auth/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/basic-auth/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/basic-auth/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/basic-auth/src/Core/Json/Utils.php delete mode 100644 seed/php-model/basic-auth/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/basic-auth/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/basic-auth/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/basic-auth/src/Core/JsonProperty.php delete mode 100644 seed/php-model/basic-auth/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/basic-auth/src/Core/SerializableType.php create mode 100644 seed/php-model/basic-auth/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/basic-auth/src/Core/Types/Constant.php create mode 100644 seed/php-model/basic-auth/src/Core/Types/DateType.php create mode 100644 seed/php-model/basic-auth/src/Core/Types/Union.php delete mode 100644 seed/php-model/basic-auth/src/Core/Union.php delete mode 100644 seed/php-model/basic-auth/src/Core/Utils.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/ArrayType.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Constant.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/DateType.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Json/Utils.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/JsonProperty.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/SerializableType.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Types/Constant.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Types/DateType.php create mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Types/Union.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Union.php delete mode 100644 seed/php-model/bearer-token-environment-variable/src/Core/Utils.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/bytes/src/Core/ArrayType.php delete mode 100644 seed/php-model/bytes/src/Core/Constant.php delete mode 100644 seed/php-model/bytes/src/Core/DateType.php create mode 100644 seed/php-model/bytes/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/bytes/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/bytes/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/bytes/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/bytes/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/bytes/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/bytes/src/Core/Json/Utils.php delete mode 100644 seed/php-model/bytes/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/bytes/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/bytes/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/bytes/src/Core/JsonProperty.php delete mode 100644 seed/php-model/bytes/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/bytes/src/Core/SerializableType.php create mode 100644 seed/php-model/bytes/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/bytes/src/Core/Types/Constant.php create mode 100644 seed/php-model/bytes/src/Core/Types/DateType.php create mode 100644 seed/php-model/bytes/src/Core/Types/Union.php delete mode 100644 seed/php-model/bytes/src/Core/Union.php delete mode 100644 seed/php-model/bytes/src/Core/Utils.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/bytes/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/ArrayType.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/Constant.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/DateType.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Json/Utils.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/JsonProperty.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/SerializableType.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Types/Constant.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Types/DateType.php create mode 100644 seed/php-model/circular-references-advanced/src/Core/Types/Union.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/Union.php delete mode 100644 seed/php-model/circular-references-advanced/src/Core/Utils.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/circular-references/src/Core/ArrayType.php delete mode 100644 seed/php-model/circular-references/src/Core/Constant.php delete mode 100644 seed/php-model/circular-references/src/Core/DateType.php create mode 100644 seed/php-model/circular-references/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/circular-references/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/circular-references/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/circular-references/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/circular-references/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/circular-references/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/circular-references/src/Core/Json/Utils.php delete mode 100644 seed/php-model/circular-references/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/circular-references/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/circular-references/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/circular-references/src/Core/JsonProperty.php delete mode 100644 seed/php-model/circular-references/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/circular-references/src/Core/SerializableType.php create mode 100644 seed/php-model/circular-references/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/circular-references/src/Core/Types/Constant.php create mode 100644 seed/php-model/circular-references/src/Core/Types/DateType.php create mode 100644 seed/php-model/circular-references/src/Core/Types/Union.php delete mode 100644 seed/php-model/circular-references/src/Core/Union.php delete mode 100644 seed/php-model/circular-references/src/Core/Utils.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/ArrayType.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/Constant.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/DateType.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Json/Utils.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/JsonProperty.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/SerializableType.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Types/Constant.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Types/DateType.php create mode 100644 seed/php-model/cross-package-type-names/src/Core/Types/Union.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/Union.php delete mode 100644 seed/php-model/cross-package-type-names/src/Core/Utils.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/custom-auth/src/Core/ArrayType.php delete mode 100644 seed/php-model/custom-auth/src/Core/Constant.php delete mode 100644 seed/php-model/custom-auth/src/Core/DateType.php create mode 100644 seed/php-model/custom-auth/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/custom-auth/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/custom-auth/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/custom-auth/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/custom-auth/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/custom-auth/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/custom-auth/src/Core/Json/Utils.php delete mode 100644 seed/php-model/custom-auth/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/custom-auth/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/custom-auth/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/custom-auth/src/Core/JsonProperty.php delete mode 100644 seed/php-model/custom-auth/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/custom-auth/src/Core/SerializableType.php create mode 100644 seed/php-model/custom-auth/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/custom-auth/src/Core/Types/Constant.php create mode 100644 seed/php-model/custom-auth/src/Core/Types/DateType.php create mode 100644 seed/php-model/custom-auth/src/Core/Types/Union.php delete mode 100644 seed/php-model/custom-auth/src/Core/Union.php delete mode 100644 seed/php-model/custom-auth/src/Core/Utils.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/enum/src/Core/ArrayType.php delete mode 100644 seed/php-model/enum/src/Core/Constant.php delete mode 100644 seed/php-model/enum/src/Core/DateType.php create mode 100644 seed/php-model/enum/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/enum/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/enum/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/enum/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/enum/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/enum/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/enum/src/Core/Json/Utils.php delete mode 100644 seed/php-model/enum/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/enum/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/enum/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/enum/src/Core/JsonProperty.php delete mode 100644 seed/php-model/enum/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/enum/src/Core/SerializableType.php create mode 100644 seed/php-model/enum/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/enum/src/Core/Types/Constant.php create mode 100644 seed/php-model/enum/src/Core/Types/DateType.php create mode 100644 seed/php-model/enum/src/Core/Types/Union.php delete mode 100644 seed/php-model/enum/src/Core/Union.php delete mode 100644 seed/php-model/enum/src/Core/Utils.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/enum/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/error-property/src/Core/ArrayType.php delete mode 100644 seed/php-model/error-property/src/Core/Constant.php delete mode 100644 seed/php-model/error-property/src/Core/DateType.php create mode 100644 seed/php-model/error-property/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/error-property/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/error-property/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/error-property/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/error-property/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/error-property/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/error-property/src/Core/Json/Utils.php delete mode 100644 seed/php-model/error-property/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/error-property/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/error-property/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/error-property/src/Core/JsonProperty.php delete mode 100644 seed/php-model/error-property/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/error-property/src/Core/SerializableType.php create mode 100644 seed/php-model/error-property/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/error-property/src/Core/Types/Constant.php create mode 100644 seed/php-model/error-property/src/Core/Types/DateType.php create mode 100644 seed/php-model/error-property/src/Core/Types/Union.php delete mode 100644 seed/php-model/error-property/src/Core/Union.php delete mode 100644 seed/php-model/error-property/src/Core/Utils.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/error-property/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/examples/src/Core/ArrayType.php delete mode 100644 seed/php-model/examples/src/Core/Constant.php delete mode 100644 seed/php-model/examples/src/Core/DateType.php create mode 100644 seed/php-model/examples/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/examples/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/examples/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/examples/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/examples/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/examples/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/examples/src/Core/Json/Utils.php delete mode 100644 seed/php-model/examples/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/examples/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/examples/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/examples/src/Core/JsonProperty.php delete mode 100644 seed/php-model/examples/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/examples/src/Core/SerializableType.php create mode 100644 seed/php-model/examples/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/examples/src/Core/Types/Constant.php create mode 100644 seed/php-model/examples/src/Core/Types/DateType.php create mode 100644 seed/php-model/examples/src/Core/Types/Union.php delete mode 100644 seed/php-model/examples/src/Core/Union.php delete mode 100644 seed/php-model/examples/src/Core/Utils.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/examples/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/exhaustive/src/Core/ArrayType.php delete mode 100644 seed/php-model/exhaustive/src/Core/Constant.php delete mode 100644 seed/php-model/exhaustive/src/Core/DateType.php create mode 100644 seed/php-model/exhaustive/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/exhaustive/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/exhaustive/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/exhaustive/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/exhaustive/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/exhaustive/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/exhaustive/src/Core/Json/Utils.php delete mode 100644 seed/php-model/exhaustive/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/exhaustive/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/exhaustive/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/exhaustive/src/Core/JsonProperty.php delete mode 100644 seed/php-model/exhaustive/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/exhaustive/src/Core/SerializableType.php create mode 100644 seed/php-model/exhaustive/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/exhaustive/src/Core/Types/Constant.php create mode 100644 seed/php-model/exhaustive/src/Core/Types/DateType.php create mode 100644 seed/php-model/exhaustive/src/Core/Types/Union.php delete mode 100644 seed/php-model/exhaustive/src/Core/Union.php delete mode 100644 seed/php-model/exhaustive/src/Core/Utils.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/extends/src/Core/ArrayType.php delete mode 100644 seed/php-model/extends/src/Core/Constant.php delete mode 100644 seed/php-model/extends/src/Core/DateType.php create mode 100644 seed/php-model/extends/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/extends/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/extends/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/extends/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/extends/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/extends/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/extends/src/Core/Json/Utils.php delete mode 100644 seed/php-model/extends/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/extends/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/extends/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/extends/src/Core/JsonProperty.php delete mode 100644 seed/php-model/extends/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/extends/src/Core/SerializableType.php create mode 100644 seed/php-model/extends/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/extends/src/Core/Types/Constant.php create mode 100644 seed/php-model/extends/src/Core/Types/DateType.php create mode 100644 seed/php-model/extends/src/Core/Types/Union.php delete mode 100644 seed/php-model/extends/src/Core/Union.php delete mode 100644 seed/php-model/extends/src/Core/Utils.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/extends/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/extra-properties/src/Core/ArrayType.php delete mode 100644 seed/php-model/extra-properties/src/Core/Constant.php delete mode 100644 seed/php-model/extra-properties/src/Core/DateType.php create mode 100644 seed/php-model/extra-properties/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/extra-properties/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/extra-properties/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/extra-properties/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/extra-properties/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/extra-properties/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/extra-properties/src/Core/Json/Utils.php delete mode 100644 seed/php-model/extra-properties/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/extra-properties/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/extra-properties/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/extra-properties/src/Core/JsonProperty.php delete mode 100644 seed/php-model/extra-properties/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/extra-properties/src/Core/SerializableType.php create mode 100644 seed/php-model/extra-properties/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/extra-properties/src/Core/Types/Constant.php create mode 100644 seed/php-model/extra-properties/src/Core/Types/DateType.php create mode 100644 seed/php-model/extra-properties/src/Core/Types/Union.php delete mode 100644 seed/php-model/extra-properties/src/Core/Union.php delete mode 100644 seed/php-model/extra-properties/src/Core/Utils.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/file-download/src/Core/ArrayType.php delete mode 100644 seed/php-model/file-download/src/Core/Constant.php delete mode 100644 seed/php-model/file-download/src/Core/DateType.php create mode 100644 seed/php-model/file-download/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/file-download/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/file-download/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/file-download/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/file-download/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/file-download/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/file-download/src/Core/Json/Utils.php delete mode 100644 seed/php-model/file-download/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/file-download/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/file-download/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/file-download/src/Core/JsonProperty.php delete mode 100644 seed/php-model/file-download/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/file-download/src/Core/SerializableType.php create mode 100644 seed/php-model/file-download/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/file-download/src/Core/Types/Constant.php create mode 100644 seed/php-model/file-download/src/Core/Types/DateType.php create mode 100644 seed/php-model/file-download/src/Core/Types/Union.php delete mode 100644 seed/php-model/file-download/src/Core/Union.php delete mode 100644 seed/php-model/file-download/src/Core/Utils.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/file-download/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/file-upload/src/Core/ArrayType.php delete mode 100644 seed/php-model/file-upload/src/Core/Constant.php delete mode 100644 seed/php-model/file-upload/src/Core/DateType.php create mode 100644 seed/php-model/file-upload/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/file-upload/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/file-upload/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/file-upload/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/file-upload/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/file-upload/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/file-upload/src/Core/Json/Utils.php delete mode 100644 seed/php-model/file-upload/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/file-upload/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/file-upload/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/file-upload/src/Core/JsonProperty.php delete mode 100644 seed/php-model/file-upload/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/file-upload/src/Core/SerializableType.php create mode 100644 seed/php-model/file-upload/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/file-upload/src/Core/Types/Constant.php create mode 100644 seed/php-model/file-upload/src/Core/Types/DateType.php create mode 100644 seed/php-model/file-upload/src/Core/Types/Union.php delete mode 100644 seed/php-model/file-upload/src/Core/Union.php delete mode 100644 seed/php-model/file-upload/src/Core/Utils.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/folders/src/Core/ArrayType.php delete mode 100644 seed/php-model/folders/src/Core/Constant.php delete mode 100644 seed/php-model/folders/src/Core/DateType.php create mode 100644 seed/php-model/folders/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/folders/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/folders/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/folders/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/folders/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/folders/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/folders/src/Core/Json/Utils.php delete mode 100644 seed/php-model/folders/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/folders/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/folders/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/folders/src/Core/JsonProperty.php delete mode 100644 seed/php-model/folders/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/folders/src/Core/SerializableType.php create mode 100644 seed/php-model/folders/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/folders/src/Core/Types/Constant.php create mode 100644 seed/php-model/folders/src/Core/Types/DateType.php create mode 100644 seed/php-model/folders/src/Core/Types/Union.php delete mode 100644 seed/php-model/folders/src/Core/Union.php delete mode 100644 seed/php-model/folders/src/Core/Utils.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/folders/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/ArrayType.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Constant.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/DateType.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Json/Utils.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/JsonProperty.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/SerializableType.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Types/Constant.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Types/DateType.php create mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Types/Union.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Union.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/src/Core/Utils.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/grpc-proto/src/Core/ArrayType.php delete mode 100644 seed/php-model/grpc-proto/src/Core/Constant.php delete mode 100644 seed/php-model/grpc-proto/src/Core/DateType.php create mode 100644 seed/php-model/grpc-proto/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/grpc-proto/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/grpc-proto/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/grpc-proto/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/grpc-proto/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/grpc-proto/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/grpc-proto/src/Core/Json/Utils.php delete mode 100644 seed/php-model/grpc-proto/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/grpc-proto/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/grpc-proto/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/grpc-proto/src/Core/JsonProperty.php delete mode 100644 seed/php-model/grpc-proto/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/grpc-proto/src/Core/SerializableType.php create mode 100644 seed/php-model/grpc-proto/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/grpc-proto/src/Core/Types/Constant.php create mode 100644 seed/php-model/grpc-proto/src/Core/Types/DateType.php create mode 100644 seed/php-model/grpc-proto/src/Core/Types/Union.php delete mode 100644 seed/php-model/grpc-proto/src/Core/Union.php delete mode 100644 seed/php-model/grpc-proto/src/Core/Utils.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/ArrayType.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/Constant.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/DateType.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Json/Utils.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/JsonProperty.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/SerializableType.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Types/Constant.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Types/DateType.php create mode 100644 seed/php-model/idempotency-headers/src/Core/Types/Union.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/Union.php delete mode 100644 seed/php-model/idempotency-headers/src/Core/Utils.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/imdb/src/Core/ArrayType.php delete mode 100644 seed/php-model/imdb/src/Core/Constant.php delete mode 100644 seed/php-model/imdb/src/Core/DateType.php create mode 100644 seed/php-model/imdb/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/imdb/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/imdb/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/imdb/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/imdb/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/imdb/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/imdb/src/Core/Json/Utils.php delete mode 100644 seed/php-model/imdb/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/imdb/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/imdb/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/imdb/src/Core/JsonProperty.php delete mode 100644 seed/php-model/imdb/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/imdb/src/Core/SerializableType.php create mode 100644 seed/php-model/imdb/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/imdb/src/Core/Types/Constant.php create mode 100644 seed/php-model/imdb/src/Core/Types/DateType.php create mode 100644 seed/php-model/imdb/src/Core/Types/Union.php delete mode 100644 seed/php-model/imdb/src/Core/Union.php delete mode 100644 seed/php-model/imdb/src/Core/Utils.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/imdb/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/literal/src/Core/ArrayType.php delete mode 100644 seed/php-model/literal/src/Core/Constant.php delete mode 100644 seed/php-model/literal/src/Core/DateType.php create mode 100644 seed/php-model/literal/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/literal/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/literal/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/literal/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/literal/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/literal/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/literal/src/Core/Json/Utils.php delete mode 100644 seed/php-model/literal/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/literal/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/literal/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/literal/src/Core/JsonProperty.php delete mode 100644 seed/php-model/literal/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/literal/src/Core/SerializableType.php create mode 100644 seed/php-model/literal/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/literal/src/Core/Types/Constant.php create mode 100644 seed/php-model/literal/src/Core/Types/DateType.php create mode 100644 seed/php-model/literal/src/Core/Types/Union.php delete mode 100644 seed/php-model/literal/src/Core/Union.php delete mode 100644 seed/php-model/literal/src/Core/Utils.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/literal/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/mixed-case/src/Core/ArrayType.php delete mode 100644 seed/php-model/mixed-case/src/Core/Constant.php delete mode 100644 seed/php-model/mixed-case/src/Core/DateType.php create mode 100644 seed/php-model/mixed-case/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/mixed-case/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/mixed-case/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/mixed-case/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/mixed-case/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/mixed-case/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/mixed-case/src/Core/Json/Utils.php delete mode 100644 seed/php-model/mixed-case/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/mixed-case/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/mixed-case/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/mixed-case/src/Core/JsonProperty.php delete mode 100644 seed/php-model/mixed-case/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/mixed-case/src/Core/SerializableType.php create mode 100644 seed/php-model/mixed-case/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/mixed-case/src/Core/Types/Constant.php create mode 100644 seed/php-model/mixed-case/src/Core/Types/DateType.php create mode 100644 seed/php-model/mixed-case/src/Core/Types/Union.php delete mode 100644 seed/php-model/mixed-case/src/Core/Union.php delete mode 100644 seed/php-model/mixed-case/src/Core/Utils.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/ArrayType.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/Constant.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/DateType.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Json/Utils.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/JsonProperty.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/SerializableType.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Types/Constant.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Types/DateType.php create mode 100644 seed/php-model/mixed-file-directory/src/Core/Types/Union.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/Union.php delete mode 100644 seed/php-model/mixed-file-directory/src/Core/Utils.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/ArrayType.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/Constant.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/DateType.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Json/Utils.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/JsonProperty.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/SerializableType.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Types/Constant.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Types/DateType.php create mode 100644 seed/php-model/multi-line-docs/src/Core/Types/Union.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/Union.php delete mode 100644 seed/php-model/multi-line-docs/src/Core/Utils.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/ArrayType.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Constant.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/DateType.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Json/Utils.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/JsonProperty.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/SerializableType.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Types/Constant.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Types/DateType.php create mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Types/Union.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Union.php delete mode 100644 seed/php-model/multi-url-environment-no-default/src/Core/Utils.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/ArrayType.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/Constant.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/DateType.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Json/Utils.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/JsonProperty.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/SerializableType.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Types/Constant.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Types/DateType.php create mode 100644 seed/php-model/multi-url-environment/src/Core/Types/Union.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/Union.php delete mode 100644 seed/php-model/multi-url-environment/src/Core/Utils.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/no-environment/src/Core/ArrayType.php delete mode 100644 seed/php-model/no-environment/src/Core/Constant.php delete mode 100644 seed/php-model/no-environment/src/Core/DateType.php create mode 100644 seed/php-model/no-environment/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/no-environment/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/no-environment/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/no-environment/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/no-environment/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/no-environment/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/no-environment/src/Core/Json/Utils.php delete mode 100644 seed/php-model/no-environment/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/no-environment/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/no-environment/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/no-environment/src/Core/JsonProperty.php delete mode 100644 seed/php-model/no-environment/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/no-environment/src/Core/SerializableType.php create mode 100644 seed/php-model/no-environment/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/no-environment/src/Core/Types/Constant.php create mode 100644 seed/php-model/no-environment/src/Core/Types/DateType.php create mode 100644 seed/php-model/no-environment/src/Core/Types/Union.php delete mode 100644 seed/php-model/no-environment/src/Core/Union.php delete mode 100644 seed/php-model/no-environment/src/Core/Utils.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/ArrayType.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Constant.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/DateType.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Json/Utils.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/JsonProperty.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/SerializableType.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Types/Constant.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Types/DateType.php create mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Types/Union.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Union.php delete mode 100644 seed/php-model/oauth-client-credentials-default/src/Core/Utils.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/ArrayType.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Constant.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/DateType.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/Utils.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonProperty.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/SerializableType.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/Constant.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/DateType.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/Union.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Union.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/src/Core/Utils.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/ArrayType.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Constant.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/DateType.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/Utils.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonProperty.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/SerializableType.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/Constant.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/DateType.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/Union.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Union.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/src/Core/Utils.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/ArrayType.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/Constant.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/DateType.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Json/Utils.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/JsonProperty.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/SerializableType.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Types/Constant.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Types/DateType.php create mode 100644 seed/php-model/oauth-client-credentials/src/Core/Types/Union.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/Union.php delete mode 100644 seed/php-model/oauth-client-credentials/src/Core/Utils.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/object/src/Core/ArrayType.php delete mode 100644 seed/php-model/object/src/Core/Constant.php delete mode 100644 seed/php-model/object/src/Core/DateType.php create mode 100644 seed/php-model/object/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/object/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/object/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/object/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/object/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/object/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/object/src/Core/Json/Utils.php delete mode 100644 seed/php-model/object/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/object/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/object/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/object/src/Core/JsonProperty.php delete mode 100644 seed/php-model/object/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/object/src/Core/SerializableType.php create mode 100644 seed/php-model/object/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/object/src/Core/Types/Constant.php create mode 100644 seed/php-model/object/src/Core/Types/DateType.php create mode 100644 seed/php-model/object/src/Core/Types/Union.php delete mode 100644 seed/php-model/object/src/Core/Union.php delete mode 100644 seed/php-model/object/src/Core/Utils.php delete mode 100644 seed/php-model/object/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/object/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/ArrayType.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/Constant.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/DateType.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Json/Utils.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/JsonProperty.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/SerializableType.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Types/Constant.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Types/DateType.php create mode 100644 seed/php-model/objects-with-imports/src/Core/Types/Union.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/Union.php delete mode 100644 seed/php-model/objects-with-imports/src/Core/Utils.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/optional/src/Core/ArrayType.php delete mode 100644 seed/php-model/optional/src/Core/Constant.php delete mode 100644 seed/php-model/optional/src/Core/DateType.php create mode 100644 seed/php-model/optional/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/optional/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/optional/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/optional/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/optional/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/optional/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/optional/src/Core/Json/Utils.php delete mode 100644 seed/php-model/optional/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/optional/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/optional/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/optional/src/Core/JsonProperty.php delete mode 100644 seed/php-model/optional/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/optional/src/Core/SerializableType.php create mode 100644 seed/php-model/optional/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/optional/src/Core/Types/Constant.php create mode 100644 seed/php-model/optional/src/Core/Types/DateType.php create mode 100644 seed/php-model/optional/src/Core/Types/Union.php delete mode 100644 seed/php-model/optional/src/Core/Union.php delete mode 100644 seed/php-model/optional/src/Core/Utils.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/optional/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/package-yml/src/Core/ArrayType.php delete mode 100644 seed/php-model/package-yml/src/Core/Constant.php delete mode 100644 seed/php-model/package-yml/src/Core/DateType.php create mode 100644 seed/php-model/package-yml/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/package-yml/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/package-yml/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/package-yml/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/package-yml/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/package-yml/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/package-yml/src/Core/Json/Utils.php delete mode 100644 seed/php-model/package-yml/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/package-yml/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/package-yml/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/package-yml/src/Core/JsonProperty.php delete mode 100644 seed/php-model/package-yml/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/package-yml/src/Core/SerializableType.php create mode 100644 seed/php-model/package-yml/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/package-yml/src/Core/Types/Constant.php create mode 100644 seed/php-model/package-yml/src/Core/Types/DateType.php create mode 100644 seed/php-model/package-yml/src/Core/Types/Union.php delete mode 100644 seed/php-model/package-yml/src/Core/Union.php delete mode 100644 seed/php-model/package-yml/src/Core/Utils.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/pagination/src/Core/ArrayType.php delete mode 100644 seed/php-model/pagination/src/Core/Constant.php delete mode 100644 seed/php-model/pagination/src/Core/DateType.php create mode 100644 seed/php-model/pagination/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/pagination/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/pagination/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/pagination/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/pagination/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/pagination/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/pagination/src/Core/Json/Utils.php delete mode 100644 seed/php-model/pagination/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/pagination/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/pagination/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/pagination/src/Core/JsonProperty.php delete mode 100644 seed/php-model/pagination/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/pagination/src/Core/SerializableType.php create mode 100644 seed/php-model/pagination/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/pagination/src/Core/Types/Constant.php create mode 100644 seed/php-model/pagination/src/Core/Types/DateType.php create mode 100644 seed/php-model/pagination/src/Core/Types/Union.php delete mode 100644 seed/php-model/pagination/src/Core/Union.php delete mode 100644 seed/php-model/pagination/src/Core/Utils.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/pagination/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/plain-text/src/Core/ArrayType.php delete mode 100644 seed/php-model/plain-text/src/Core/Constant.php delete mode 100644 seed/php-model/plain-text/src/Core/DateType.php create mode 100644 seed/php-model/plain-text/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/plain-text/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/plain-text/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/plain-text/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/plain-text/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/plain-text/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/plain-text/src/Core/Json/Utils.php delete mode 100644 seed/php-model/plain-text/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/plain-text/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/plain-text/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/plain-text/src/Core/JsonProperty.php delete mode 100644 seed/php-model/plain-text/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/plain-text/src/Core/SerializableType.php create mode 100644 seed/php-model/plain-text/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/plain-text/src/Core/Types/Constant.php create mode 100644 seed/php-model/plain-text/src/Core/Types/DateType.php create mode 100644 seed/php-model/plain-text/src/Core/Types/Union.php delete mode 100644 seed/php-model/plain-text/src/Core/Union.php delete mode 100644 seed/php-model/plain-text/src/Core/Utils.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/query-parameters/src/Core/ArrayType.php delete mode 100644 seed/php-model/query-parameters/src/Core/Constant.php delete mode 100644 seed/php-model/query-parameters/src/Core/DateType.php create mode 100644 seed/php-model/query-parameters/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/query-parameters/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/query-parameters/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/query-parameters/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/query-parameters/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/query-parameters/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/query-parameters/src/Core/Json/Utils.php delete mode 100644 seed/php-model/query-parameters/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/query-parameters/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/query-parameters/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/query-parameters/src/Core/JsonProperty.php delete mode 100644 seed/php-model/query-parameters/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/query-parameters/src/Core/SerializableType.php create mode 100644 seed/php-model/query-parameters/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/query-parameters/src/Core/Types/Constant.php create mode 100644 seed/php-model/query-parameters/src/Core/Types/DateType.php create mode 100644 seed/php-model/query-parameters/src/Core/Types/Union.php delete mode 100644 seed/php-model/query-parameters/src/Core/Union.php delete mode 100644 seed/php-model/query-parameters/src/Core/Utils.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/ArrayType.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/Constant.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/DateType.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Json/Utils.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/JsonProperty.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/SerializableType.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Types/Constant.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Types/DateType.php create mode 100644 seed/php-model/reserved-keywords/src/Core/Types/Union.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/Union.php delete mode 100644 seed/php-model/reserved-keywords/src/Core/Utils.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/response-property/src/Core/ArrayType.php delete mode 100644 seed/php-model/response-property/src/Core/Constant.php delete mode 100644 seed/php-model/response-property/src/Core/DateType.php create mode 100644 seed/php-model/response-property/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/response-property/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/response-property/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/response-property/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/response-property/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/response-property/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/response-property/src/Core/Json/Utils.php delete mode 100644 seed/php-model/response-property/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/response-property/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/response-property/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/response-property/src/Core/JsonProperty.php delete mode 100644 seed/php-model/response-property/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/response-property/src/Core/SerializableType.php create mode 100644 seed/php-model/response-property/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/response-property/src/Core/Types/Constant.php create mode 100644 seed/php-model/response-property/src/Core/Types/DateType.php create mode 100644 seed/php-model/response-property/src/Core/Types/Union.php delete mode 100644 seed/php-model/response-property/src/Core/Union.php delete mode 100644 seed/php-model/response-property/src/Core/Utils.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/response-property/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/ArrayType.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/Constant.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/DateType.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Json/Utils.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonProperty.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/SerializableType.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Types/Constant.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Types/DateType.php create mode 100644 seed/php-model/server-sent-event-examples/src/Core/Types/Union.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/Union.php delete mode 100644 seed/php-model/server-sent-event-examples/src/Core/Utils.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/server-sent-events/src/Core/ArrayType.php delete mode 100644 seed/php-model/server-sent-events/src/Core/Constant.php delete mode 100644 seed/php-model/server-sent-events/src/Core/DateType.php create mode 100644 seed/php-model/server-sent-events/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/server-sent-events/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/server-sent-events/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/server-sent-events/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/server-sent-events/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/server-sent-events/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/server-sent-events/src/Core/Json/Utils.php delete mode 100644 seed/php-model/server-sent-events/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/server-sent-events/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/server-sent-events/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/server-sent-events/src/Core/JsonProperty.php delete mode 100644 seed/php-model/server-sent-events/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/server-sent-events/src/Core/SerializableType.php create mode 100644 seed/php-model/server-sent-events/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/server-sent-events/src/Core/Types/Constant.php create mode 100644 seed/php-model/server-sent-events/src/Core/Types/DateType.php create mode 100644 seed/php-model/server-sent-events/src/Core/Types/Union.php delete mode 100644 seed/php-model/server-sent-events/src/Core/Union.php delete mode 100644 seed/php-model/server-sent-events/src/Core/Utils.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/simple-fhir/src/Core/ArrayType.php delete mode 100644 seed/php-model/simple-fhir/src/Core/Constant.php delete mode 100644 seed/php-model/simple-fhir/src/Core/DateType.php create mode 100644 seed/php-model/simple-fhir/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/simple-fhir/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/simple-fhir/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/simple-fhir/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/simple-fhir/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/simple-fhir/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/simple-fhir/src/Core/Json/Utils.php delete mode 100644 seed/php-model/simple-fhir/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/simple-fhir/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/simple-fhir/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/simple-fhir/src/Core/JsonProperty.php delete mode 100644 seed/php-model/simple-fhir/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/simple-fhir/src/Core/SerializableType.php create mode 100644 seed/php-model/simple-fhir/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/simple-fhir/src/Core/Types/Constant.php create mode 100644 seed/php-model/simple-fhir/src/Core/Types/DateType.php create mode 100644 seed/php-model/simple-fhir/src/Core/Types/Union.php delete mode 100644 seed/php-model/simple-fhir/src/Core/Union.php delete mode 100644 seed/php-model/simple-fhir/src/Core/Utils.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/ArrayType.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/Constant.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/DateType.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Json/Utils.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/JsonProperty.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/SerializableType.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Types/Constant.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Types/DateType.php create mode 100644 seed/php-model/single-url-environment-default/src/Core/Types/Union.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/Union.php delete mode 100644 seed/php-model/single-url-environment-default/src/Core/Utils.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/ArrayType.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Constant.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/DateType.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Json/Utils.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/JsonProperty.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/SerializableType.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Types/Constant.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Types/DateType.php create mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Types/Union.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Union.php delete mode 100644 seed/php-model/single-url-environment-no-default/src/Core/Utils.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/ArrayType.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/Constant.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/DateType.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Json/Utils.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/JsonProperty.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/SerializableType.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Types/Constant.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Types/DateType.php create mode 100644 seed/php-model/streaming-parameter/src/Core/Types/Union.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/Union.php delete mode 100644 seed/php-model/streaming-parameter/src/Core/Utils.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/streaming/src/Core/ArrayType.php delete mode 100644 seed/php-model/streaming/src/Core/Constant.php delete mode 100644 seed/php-model/streaming/src/Core/DateType.php create mode 100644 seed/php-model/streaming/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/streaming/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/streaming/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/streaming/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/streaming/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/streaming/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/streaming/src/Core/Json/Utils.php delete mode 100644 seed/php-model/streaming/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/streaming/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/streaming/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/streaming/src/Core/JsonProperty.php delete mode 100644 seed/php-model/streaming/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/streaming/src/Core/SerializableType.php create mode 100644 seed/php-model/streaming/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/streaming/src/Core/Types/Constant.php create mode 100644 seed/php-model/streaming/src/Core/Types/DateType.php create mode 100644 seed/php-model/streaming/src/Core/Types/Union.php delete mode 100644 seed/php-model/streaming/src/Core/Union.php delete mode 100644 seed/php-model/streaming/src/Core/Utils.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/streaming/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/trace/src/Core/ArrayType.php delete mode 100644 seed/php-model/trace/src/Core/Constant.php delete mode 100644 seed/php-model/trace/src/Core/DateType.php create mode 100644 seed/php-model/trace/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/trace/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/trace/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/trace/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/trace/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/trace/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/trace/src/Core/Json/Utils.php delete mode 100644 seed/php-model/trace/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/trace/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/trace/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/trace/src/Core/JsonProperty.php delete mode 100644 seed/php-model/trace/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/trace/src/Core/SerializableType.php create mode 100644 seed/php-model/trace/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/trace/src/Core/Types/Constant.php create mode 100644 seed/php-model/trace/src/Core/Types/DateType.php create mode 100644 seed/php-model/trace/src/Core/Types/Union.php delete mode 100644 seed/php-model/trace/src/Core/Union.php delete mode 100644 seed/php-model/trace/src/Core/Utils.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/trace/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/ArrayType.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/Constant.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/DateType.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Json/Utils.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/JsonProperty.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/SerializableType.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Types/Constant.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Types/DateType.php create mode 100644 seed/php-model/undiscriminated-unions/src/Core/Types/Union.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/Union.php delete mode 100644 seed/php-model/undiscriminated-unions/src/Core/Utils.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/unions/src/Core/ArrayType.php delete mode 100644 seed/php-model/unions/src/Core/Constant.php delete mode 100644 seed/php-model/unions/src/Core/DateType.php create mode 100644 seed/php-model/unions/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/unions/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/unions/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/unions/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/unions/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/unions/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/unions/src/Core/Json/Utils.php delete mode 100644 seed/php-model/unions/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/unions/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/unions/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/unions/src/Core/JsonProperty.php delete mode 100644 seed/php-model/unions/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/unions/src/Core/SerializableType.php create mode 100644 seed/php-model/unions/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/unions/src/Core/Types/Constant.php create mode 100644 seed/php-model/unions/src/Core/Types/DateType.php create mode 100644 seed/php-model/unions/src/Core/Types/Union.php delete mode 100644 seed/php-model/unions/src/Core/Union.php delete mode 100644 seed/php-model/unions/src/Core/Utils.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/unions/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/unknown/src/Core/ArrayType.php delete mode 100644 seed/php-model/unknown/src/Core/Constant.php delete mode 100644 seed/php-model/unknown/src/Core/DateType.php create mode 100644 seed/php-model/unknown/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/unknown/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/unknown/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/unknown/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/unknown/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/unknown/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/unknown/src/Core/Json/Utils.php delete mode 100644 seed/php-model/unknown/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/unknown/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/unknown/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/unknown/src/Core/JsonProperty.php delete mode 100644 seed/php-model/unknown/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/unknown/src/Core/SerializableType.php create mode 100644 seed/php-model/unknown/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/unknown/src/Core/Types/Constant.php create mode 100644 seed/php-model/unknown/src/Core/Types/DateType.php create mode 100644 seed/php-model/unknown/src/Core/Types/Union.php delete mode 100644 seed/php-model/unknown/src/Core/Union.php delete mode 100644 seed/php-model/unknown/src/Core/Utils.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/unknown/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/validation/src/Core/ArrayType.php delete mode 100644 seed/php-model/validation/src/Core/Constant.php delete mode 100644 seed/php-model/validation/src/Core/DateType.php create mode 100644 seed/php-model/validation/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/validation/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/validation/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/validation/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/validation/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/validation/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/validation/src/Core/Json/Utils.php delete mode 100644 seed/php-model/validation/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/validation/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/validation/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/validation/src/Core/JsonProperty.php delete mode 100644 seed/php-model/validation/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/validation/src/Core/SerializableType.php create mode 100644 seed/php-model/validation/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/validation/src/Core/Types/Constant.php create mode 100644 seed/php-model/validation/src/Core/Types/DateType.php create mode 100644 seed/php-model/validation/src/Core/Types/Union.php delete mode 100644 seed/php-model/validation/src/Core/Union.php delete mode 100644 seed/php-model/validation/src/Core/Utils.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/validation/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/variables/src/Core/ArrayType.php delete mode 100644 seed/php-model/variables/src/Core/Constant.php delete mode 100644 seed/php-model/variables/src/Core/DateType.php create mode 100644 seed/php-model/variables/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/variables/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/variables/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/variables/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/variables/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/variables/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/variables/src/Core/Json/Utils.php delete mode 100644 seed/php-model/variables/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/variables/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/variables/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/variables/src/Core/JsonProperty.php delete mode 100644 seed/php-model/variables/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/variables/src/Core/SerializableType.php create mode 100644 seed/php-model/variables/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/variables/src/Core/Types/Constant.php create mode 100644 seed/php-model/variables/src/Core/Types/DateType.php create mode 100644 seed/php-model/variables/src/Core/Types/Union.php delete mode 100644 seed/php-model/variables/src/Core/Union.php delete mode 100644 seed/php-model/variables/src/Core/Utils.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/variables/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/version-no-default/src/Core/ArrayType.php delete mode 100644 seed/php-model/version-no-default/src/Core/Constant.php delete mode 100644 seed/php-model/version-no-default/src/Core/DateType.php create mode 100644 seed/php-model/version-no-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/version-no-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/version-no-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/version-no-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/version-no-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/version-no-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/version-no-default/src/Core/Json/Utils.php delete mode 100644 seed/php-model/version-no-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/version-no-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/version-no-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/version-no-default/src/Core/JsonProperty.php delete mode 100644 seed/php-model/version-no-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/version-no-default/src/Core/SerializableType.php create mode 100644 seed/php-model/version-no-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/version-no-default/src/Core/Types/Constant.php create mode 100644 seed/php-model/version-no-default/src/Core/Types/DateType.php create mode 100644 seed/php-model/version-no-default/src/Core/Types/Union.php delete mode 100644 seed/php-model/version-no-default/src/Core/Union.php delete mode 100644 seed/php-model/version-no-default/src/Core/Utils.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/version/src/Core/ArrayType.php delete mode 100644 seed/php-model/version/src/Core/Constant.php delete mode 100644 seed/php-model/version/src/Core/DateType.php create mode 100644 seed/php-model/version/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/version/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/version/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/version/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/version/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/version/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/version/src/Core/Json/Utils.php delete mode 100644 seed/php-model/version/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/version/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/version/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/version/src/Core/JsonProperty.php delete mode 100644 seed/php-model/version/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/version/src/Core/SerializableType.php create mode 100644 seed/php-model/version/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/version/src/Core/Types/Constant.php create mode 100644 seed/php-model/version/src/Core/Types/DateType.php create mode 100644 seed/php-model/version/src/Core/Types/Union.php delete mode 100644 seed/php-model/version/src/Core/Union.php delete mode 100644 seed/php-model/version/src/Core/Utils.php delete mode 100644 seed/php-model/version/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/version/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/websocket/src/Core/ArrayType.php delete mode 100644 seed/php-model/websocket/src/Core/Constant.php delete mode 100644 seed/php-model/websocket/src/Core/DateType.php create mode 100644 seed/php-model/websocket/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/websocket/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/websocket/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/websocket/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/websocket/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/websocket/src/Core/Json/SerializableType.php create mode 100644 seed/php-model/websocket/src/Core/Json/Utils.php delete mode 100644 seed/php-model/websocket/src/Core/JsonDecoder.php delete mode 100644 seed/php-model/websocket/src/Core/JsonDeserializer.php delete mode 100644 seed/php-model/websocket/src/Core/JsonEncoder.php delete mode 100644 seed/php-model/websocket/src/Core/JsonProperty.php delete mode 100644 seed/php-model/websocket/src/Core/JsonSerializer.php delete mode 100644 seed/php-model/websocket/src/Core/SerializableType.php create mode 100644 seed/php-model/websocket/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/websocket/src/Core/Types/Constant.php create mode 100644 seed/php-model/websocket/src/Core/Types/DateType.php create mode 100644 seed/php-model/websocket/src/Core/Types/Union.php delete mode 100644 seed/php-model/websocket/src/Core/Union.php delete mode 100644 seed/php-model/websocket/src/Core/Utils.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-model/websocket/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/Constant.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/DateType.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/RawClient.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/SerializableType.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/alias-extends/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/Union.php delete mode 100644 seed/php-sdk/alias-extends/src/Core/Utils.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/alias/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/alias/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/alias/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/alias/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/alias/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/alias/src/Core/Constant.php delete mode 100644 seed/php-sdk/alias/src/Core/DateType.php delete mode 100644 seed/php-sdk/alias/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/alias/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/alias/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/alias/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/alias/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/alias/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/alias/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/alias/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/alias/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/alias/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/alias/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/alias/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/alias/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/alias/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/alias/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/alias/src/Core/RawClient.php delete mode 100644 seed/php-sdk/alias/src/Core/SerializableType.php create mode 100644 seed/php-sdk/alias/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/alias/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/alias/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/alias/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/alias/src/Core/Union.php delete mode 100644 seed/php-sdk/alias/src/Core/Utils.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/alias/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/any-auth/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/any-auth/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/any-auth/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/any-auth/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/any-auth/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/any-auth/src/Core/Constant.php delete mode 100644 seed/php-sdk/any-auth/src/Core/DateType.php delete mode 100644 seed/php-sdk/any-auth/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/any-auth/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/any-auth/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/any-auth/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/any-auth/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/any-auth/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/any-auth/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/any-auth/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/any-auth/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/any-auth/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/any-auth/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/any-auth/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/any-auth/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/any-auth/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/any-auth/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/any-auth/src/Core/RawClient.php delete mode 100644 seed/php-sdk/any-auth/src/Core/SerializableType.php create mode 100644 seed/php-sdk/any-auth/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/any-auth/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/any-auth/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/any-auth/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/any-auth/src/Core/Union.php delete mode 100644 seed/php-sdk/any-auth/src/Core/Utils.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Constant.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/DateType.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/RawClient.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/SerializableType.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Union.php delete mode 100644 seed/php-sdk/api-wide-base-path/src/Core/Utils.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/audiences/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/audiences/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/audiences/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/audiences/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/audiences/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/audiences/src/Core/Constant.php delete mode 100644 seed/php-sdk/audiences/src/Core/DateType.php delete mode 100644 seed/php-sdk/audiences/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/audiences/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/audiences/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/audiences/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/audiences/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/audiences/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/audiences/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/audiences/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/audiences/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/audiences/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/audiences/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/audiences/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/audiences/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/audiences/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/audiences/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/audiences/src/Core/RawClient.php delete mode 100644 seed/php-sdk/audiences/src/Core/SerializableType.php create mode 100644 seed/php-sdk/audiences/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/audiences/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/audiences/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/audiences/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/audiences/src/Core/Union.php delete mode 100644 seed/php-sdk/audiences/src/Core/Utils.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/audiences/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Constant.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/DateType.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/RawClient.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/SerializableType.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Union.php delete mode 100644 seed/php-sdk/auth-environment-variables/src/Core/Utils.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Constant.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/DateType.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/RawClient.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/SerializableType.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Union.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/src/Core/Utils.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/Constant.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/DateType.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/RawClient.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/SerializableType.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/basic-auth/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/Union.php delete mode 100644 seed/php-sdk/basic-auth/src/Core/Utils.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Constant.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/DateType.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/RawClient.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/SerializableType.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Union.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/src/Core/Utils.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/bytes/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/bytes/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/bytes/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/bytes/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/bytes/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/bytes/src/Core/Constant.php delete mode 100644 seed/php-sdk/bytes/src/Core/DateType.php delete mode 100644 seed/php-sdk/bytes/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/bytes/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/bytes/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/bytes/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/bytes/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/bytes/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/bytes/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/bytes/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/bytes/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/bytes/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/bytes/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/bytes/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/bytes/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/bytes/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/bytes/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/bytes/src/Core/RawClient.php delete mode 100644 seed/php-sdk/bytes/src/Core/SerializableType.php create mode 100644 seed/php-sdk/bytes/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/bytes/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/bytes/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/bytes/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/bytes/src/Core/Union.php delete mode 100644 seed/php-sdk/bytes/src/Core/Utils.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/bytes/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Constant.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/DateType.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/RawClient.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/SerializableType.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Union.php delete mode 100644 seed/php-sdk/circular-references-advanced/src/Core/Utils.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/circular-references/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/circular-references/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/circular-references/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/circular-references/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/circular-references/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/circular-references/src/Core/Constant.php delete mode 100644 seed/php-sdk/circular-references/src/Core/DateType.php delete mode 100644 seed/php-sdk/circular-references/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/circular-references/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/circular-references/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/circular-references/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/circular-references/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/circular-references/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/circular-references/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/circular-references/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/circular-references/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/circular-references/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/circular-references/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/circular-references/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/circular-references/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/circular-references/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/circular-references/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/circular-references/src/Core/RawClient.php delete mode 100644 seed/php-sdk/circular-references/src/Core/SerializableType.php create mode 100644 seed/php-sdk/circular-references/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/circular-references/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/circular-references/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/circular-references/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/circular-references/src/Core/Union.php delete mode 100644 seed/php-sdk/circular-references/src/Core/Utils.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Constant.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/DateType.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/RawClient.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/SerializableType.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Union.php delete mode 100644 seed/php-sdk/cross-package-type-names/src/Core/Utils.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/Constant.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/DateType.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/RawClient.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/SerializableType.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/custom-auth/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/Union.php delete mode 100644 seed/php-sdk/custom-auth/src/Core/Utils.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/enum/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/enum/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/enum/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/enum/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/enum/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/enum/src/Core/Constant.php delete mode 100644 seed/php-sdk/enum/src/Core/DateType.php delete mode 100644 seed/php-sdk/enum/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/enum/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/enum/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/enum/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/enum/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/enum/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/enum/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/enum/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/enum/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/enum/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/enum/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/enum/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/enum/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/enum/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/enum/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/enum/src/Core/RawClient.php delete mode 100644 seed/php-sdk/enum/src/Core/SerializableType.php create mode 100644 seed/php-sdk/enum/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/enum/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/enum/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/enum/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/enum/src/Core/Union.php delete mode 100644 seed/php-sdk/enum/src/Core/Utils.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/enum/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/error-property/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/error-property/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/error-property/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/error-property/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/error-property/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/error-property/src/Core/Constant.php delete mode 100644 seed/php-sdk/error-property/src/Core/DateType.php delete mode 100644 seed/php-sdk/error-property/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/error-property/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/error-property/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/error-property/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/error-property/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/error-property/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/error-property/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/error-property/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/error-property/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/error-property/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/error-property/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/error-property/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/error-property/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/error-property/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/error-property/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/error-property/src/Core/RawClient.php delete mode 100644 seed/php-sdk/error-property/src/Core/SerializableType.php create mode 100644 seed/php-sdk/error-property/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/error-property/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/error-property/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/error-property/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/error-property/src/Core/Union.php delete mode 100644 seed/php-sdk/error-property/src/Core/Utils.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/error-property/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/examples/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/examples/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/examples/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/examples/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/examples/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/examples/src/Core/Constant.php delete mode 100644 seed/php-sdk/examples/src/Core/DateType.php delete mode 100644 seed/php-sdk/examples/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/examples/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/examples/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/examples/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/examples/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/examples/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/examples/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/examples/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/examples/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/examples/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/examples/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/examples/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/examples/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/examples/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/examples/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/examples/src/Core/RawClient.php delete mode 100644 seed/php-sdk/examples/src/Core/SerializableType.php create mode 100644 seed/php-sdk/examples/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/examples/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/examples/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/examples/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/examples/src/Core/Union.php delete mode 100644 seed/php-sdk/examples/src/Core/Utils.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/examples/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/Constant.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/DateType.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/RawClient.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/SerializableType.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/exhaustive/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/Union.php delete mode 100644 seed/php-sdk/exhaustive/src/Core/Utils.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/extends/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/extends/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/extends/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/extends/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/extends/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/extends/src/Core/Constant.php delete mode 100644 seed/php-sdk/extends/src/Core/DateType.php delete mode 100644 seed/php-sdk/extends/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/extends/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/extends/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/extends/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/extends/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/extends/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/extends/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/extends/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/extends/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/extends/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/extends/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/extends/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/extends/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/extends/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/extends/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/extends/src/Core/RawClient.php delete mode 100644 seed/php-sdk/extends/src/Core/SerializableType.php create mode 100644 seed/php-sdk/extends/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/extends/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/extends/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/extends/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/extends/src/Core/Union.php delete mode 100644 seed/php-sdk/extends/src/Core/Utils.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/extends/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/Constant.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/DateType.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/RawClient.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/SerializableType.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/extra-properties/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/Union.php delete mode 100644 seed/php-sdk/extra-properties/src/Core/Utils.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/file-download/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/file-download/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/file-download/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/file-download/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/file-download/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/file-download/src/Core/Constant.php delete mode 100644 seed/php-sdk/file-download/src/Core/DateType.php delete mode 100644 seed/php-sdk/file-download/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/file-download/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/file-download/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/file-download/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/file-download/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/file-download/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/file-download/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/file-download/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/file-download/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/file-download/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/file-download/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/file-download/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/file-download/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/file-download/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/file-download/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/file-download/src/Core/RawClient.php delete mode 100644 seed/php-sdk/file-download/src/Core/SerializableType.php create mode 100644 seed/php-sdk/file-download/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/file-download/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/file-download/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/file-download/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/file-download/src/Core/Union.php delete mode 100644 seed/php-sdk/file-download/src/Core/Utils.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/file-download/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/file-upload/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/file-upload/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/file-upload/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/file-upload/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/file-upload/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/file-upload/src/Core/Constant.php delete mode 100644 seed/php-sdk/file-upload/src/Core/DateType.php delete mode 100644 seed/php-sdk/file-upload/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/file-upload/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/file-upload/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/file-upload/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/file-upload/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/file-upload/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/file-upload/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/file-upload/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/file-upload/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/file-upload/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/file-upload/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/file-upload/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/file-upload/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/file-upload/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/file-upload/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/file-upload/src/Core/RawClient.php delete mode 100644 seed/php-sdk/file-upload/src/Core/SerializableType.php create mode 100644 seed/php-sdk/file-upload/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/file-upload/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/file-upload/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/file-upload/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/file-upload/src/Core/Union.php delete mode 100644 seed/php-sdk/file-upload/src/Core/Utils.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/folders/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/folders/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/folders/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/folders/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/folders/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/folders/src/Core/Constant.php delete mode 100644 seed/php-sdk/folders/src/Core/DateType.php delete mode 100644 seed/php-sdk/folders/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/folders/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/folders/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/folders/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/folders/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/folders/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/folders/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/folders/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/folders/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/folders/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/folders/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/folders/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/folders/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/folders/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/folders/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/folders/src/Core/RawClient.php delete mode 100644 seed/php-sdk/folders/src/Core/SerializableType.php create mode 100644 seed/php-sdk/folders/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/folders/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/folders/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/folders/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/folders/src/Core/Union.php delete mode 100644 seed/php-sdk/folders/src/Core/Utils.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/folders/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Constant.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/DateType.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/RawClient.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/SerializableType.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Union.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/src/Core/Utils.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/Constant.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/DateType.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/RawClient.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/SerializableType.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/grpc-proto/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/Union.php delete mode 100644 seed/php-sdk/grpc-proto/src/Core/Utils.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/Constant.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/DateType.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/RawClient.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/SerializableType.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/idempotency-headers/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/Union.php delete mode 100644 seed/php-sdk/idempotency-headers/src/Core/Utils.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/imdb/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/imdb/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/imdb/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/imdb/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/imdb/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/imdb/src/Core/Constant.php delete mode 100644 seed/php-sdk/imdb/src/Core/DateType.php delete mode 100644 seed/php-sdk/imdb/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/imdb/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/imdb/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/imdb/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/imdb/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/imdb/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/imdb/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/imdb/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/imdb/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/imdb/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/imdb/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/imdb/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/imdb/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/imdb/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/imdb/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/imdb/src/Core/RawClient.php delete mode 100644 seed/php-sdk/imdb/src/Core/SerializableType.php create mode 100644 seed/php-sdk/imdb/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/imdb/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/imdb/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/imdb/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/imdb/src/Core/Union.php delete mode 100644 seed/php-sdk/imdb/src/Core/Utils.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/imdb/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/literal/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/literal/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/literal/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/literal/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/literal/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/literal/src/Core/Constant.php delete mode 100644 seed/php-sdk/literal/src/Core/DateType.php delete mode 100644 seed/php-sdk/literal/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/literal/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/literal/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/literal/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/literal/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/literal/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/literal/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/literal/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/literal/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/literal/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/literal/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/literal/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/literal/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/literal/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/literal/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/literal/src/Core/RawClient.php delete mode 100644 seed/php-sdk/literal/src/Core/SerializableType.php create mode 100644 seed/php-sdk/literal/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/literal/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/literal/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/literal/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/literal/src/Core/Union.php delete mode 100644 seed/php-sdk/literal/src/Core/Utils.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/literal/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/Constant.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/DateType.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/RawClient.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/SerializableType.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/mixed-case/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/Union.php delete mode 100644 seed/php-sdk/mixed-case/src/Core/Utils.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Constant.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/DateType.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/RawClient.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/SerializableType.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Union.php delete mode 100644 seed/php-sdk/mixed-file-directory/src/Core/Utils.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/Constant.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/DateType.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/RawClient.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/SerializableType.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/multi-line-docs/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/Union.php delete mode 100644 seed/php-sdk/multi-line-docs/src/Core/Utils.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/no-environment/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/no-environment/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/no-environment/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/no-environment/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/no-environment/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/no-environment/src/Core/Constant.php delete mode 100644 seed/php-sdk/no-environment/src/Core/DateType.php delete mode 100644 seed/php-sdk/no-environment/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/no-environment/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/no-environment/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/no-environment/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/no-environment/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/no-environment/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/no-environment/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/no-environment/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/no-environment/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/no-environment/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/no-environment/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/no-environment/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/no-environment/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/no-environment/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/no-environment/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/no-environment/src/Core/RawClient.php delete mode 100644 seed/php-sdk/no-environment/src/Core/SerializableType.php create mode 100644 seed/php-sdk/no-environment/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/no-environment/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/no-environment/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/no-environment/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/no-environment/src/Core/Union.php delete mode 100644 seed/php-sdk/no-environment/src/Core/Utils.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Constant.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/DateType.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/RawClient.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/SerializableType.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Union.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/src/Core/Utils.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Constant.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/DateType.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/RawClient.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/SerializableType.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Union.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Utils.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Constant.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/DateType.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/RawClient.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/SerializableType.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Union.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Utils.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Constant.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/DateType.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/RawClient.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/SerializableType.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Union.php delete mode 100644 seed/php-sdk/oauth-client-credentials/src/Core/Utils.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/object/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/object/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/object/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/object/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/object/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/object/src/Core/Constant.php delete mode 100644 seed/php-sdk/object/src/Core/DateType.php delete mode 100644 seed/php-sdk/object/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/object/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/object/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/object/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/object/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/object/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/object/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/object/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/object/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/object/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/object/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/object/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/object/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/object/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/object/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/object/src/Core/RawClient.php delete mode 100644 seed/php-sdk/object/src/Core/SerializableType.php create mode 100644 seed/php-sdk/object/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/object/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/object/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/object/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/object/src/Core/Union.php delete mode 100644 seed/php-sdk/object/src/Core/Utils.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/object/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/Constant.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/DateType.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/RawClient.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/SerializableType.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/objects-with-imports/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/Union.php delete mode 100644 seed/php-sdk/objects-with-imports/src/Core/Utils.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/optional/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/optional/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/optional/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/optional/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/optional/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/optional/src/Core/Constant.php delete mode 100644 seed/php-sdk/optional/src/Core/DateType.php delete mode 100644 seed/php-sdk/optional/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/optional/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/optional/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/optional/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/optional/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/optional/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/optional/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/optional/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/optional/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/optional/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/optional/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/optional/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/optional/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/optional/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/optional/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/optional/src/Core/RawClient.php delete mode 100644 seed/php-sdk/optional/src/Core/SerializableType.php create mode 100644 seed/php-sdk/optional/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/optional/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/optional/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/optional/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/optional/src/Core/Union.php delete mode 100644 seed/php-sdk/optional/src/Core/Utils.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/optional/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/package-yml/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/package-yml/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/package-yml/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/package-yml/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/package-yml/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/package-yml/src/Core/Constant.php delete mode 100644 seed/php-sdk/package-yml/src/Core/DateType.php delete mode 100644 seed/php-sdk/package-yml/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/package-yml/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/package-yml/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/package-yml/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/package-yml/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/package-yml/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/package-yml/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/package-yml/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/package-yml/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/package-yml/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/package-yml/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/package-yml/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/package-yml/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/package-yml/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/package-yml/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/package-yml/src/Core/RawClient.php delete mode 100644 seed/php-sdk/package-yml/src/Core/SerializableType.php create mode 100644 seed/php-sdk/package-yml/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/package-yml/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/package-yml/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/package-yml/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/package-yml/src/Core/Union.php delete mode 100644 seed/php-sdk/package-yml/src/Core/Utils.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/pagination/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/pagination/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/pagination/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/pagination/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/pagination/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/pagination/src/Core/Constant.php delete mode 100644 seed/php-sdk/pagination/src/Core/DateType.php delete mode 100644 seed/php-sdk/pagination/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/pagination/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/pagination/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/pagination/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/pagination/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/pagination/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/pagination/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/pagination/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/pagination/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/pagination/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/pagination/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/pagination/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/pagination/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/pagination/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/pagination/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/pagination/src/Core/RawClient.php delete mode 100644 seed/php-sdk/pagination/src/Core/SerializableType.php create mode 100644 seed/php-sdk/pagination/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/pagination/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/pagination/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/pagination/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/pagination/src/Core/Union.php delete mode 100644 seed/php-sdk/pagination/src/Core/Utils.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/pagination/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/plain-text/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/plain-text/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/plain-text/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/plain-text/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/plain-text/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/plain-text/src/Core/Constant.php delete mode 100644 seed/php-sdk/plain-text/src/Core/DateType.php delete mode 100644 seed/php-sdk/plain-text/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/plain-text/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/plain-text/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/plain-text/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/plain-text/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/plain-text/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/plain-text/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/plain-text/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/plain-text/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/plain-text/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/plain-text/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/plain-text/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/plain-text/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/plain-text/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/plain-text/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/plain-text/src/Core/RawClient.php delete mode 100644 seed/php-sdk/plain-text/src/Core/SerializableType.php create mode 100644 seed/php-sdk/plain-text/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/plain-text/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/plain-text/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/plain-text/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/plain-text/src/Core/Union.php delete mode 100644 seed/php-sdk/plain-text/src/Core/Utils.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/Constant.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/DateType.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/RawClient.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/SerializableType.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/query-parameters/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/Union.php delete mode 100644 seed/php-sdk/query-parameters/src/Core/Utils.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/Constant.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/DateType.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/RawClient.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/SerializableType.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/reserved-keywords/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/Union.php delete mode 100644 seed/php-sdk/reserved-keywords/src/Core/Utils.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/response-property/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/response-property/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/response-property/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/response-property/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/response-property/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/response-property/src/Core/Constant.php delete mode 100644 seed/php-sdk/response-property/src/Core/DateType.php delete mode 100644 seed/php-sdk/response-property/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/response-property/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/response-property/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/response-property/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/response-property/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/response-property/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/response-property/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/response-property/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/response-property/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/response-property/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/response-property/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/response-property/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/response-property/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/response-property/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/response-property/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/response-property/src/Core/RawClient.php delete mode 100644 seed/php-sdk/response-property/src/Core/SerializableType.php create mode 100644 seed/php-sdk/response-property/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/response-property/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/response-property/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/response-property/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/response-property/src/Core/Union.php delete mode 100644 seed/php-sdk/response-property/src/Core/Utils.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/response-property/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Constant.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/DateType.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/RawClient.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/SerializableType.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Union.php delete mode 100644 seed/php-sdk/server-sent-event-examples/src/Core/Utils.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/Constant.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/DateType.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/RawClient.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/SerializableType.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/server-sent-events/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/Union.php delete mode 100644 seed/php-sdk/server-sent-events/src/Core/Utils.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/Constant.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/DateType.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/RawClient.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/SerializableType.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/simple-fhir/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/Union.php delete mode 100644 seed/php-sdk/simple-fhir/src/Core/Utils.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Constant.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/DateType.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/RawClient.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/SerializableType.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Union.php delete mode 100644 seed/php-sdk/single-url-environment-default/src/Core/Utils.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Constant.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/DateType.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/RawClient.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/SerializableType.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Union.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/src/Core/Utils.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/Constant.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/DateType.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/RawClient.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/SerializableType.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/streaming-parameter/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/Union.php delete mode 100644 seed/php-sdk/streaming-parameter/src/Core/Utils.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/streaming/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/streaming/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/streaming/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/streaming/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/streaming/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/streaming/src/Core/Constant.php delete mode 100644 seed/php-sdk/streaming/src/Core/DateType.php delete mode 100644 seed/php-sdk/streaming/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/streaming/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/streaming/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/streaming/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/streaming/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/streaming/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/streaming/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/streaming/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/streaming/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/streaming/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/streaming/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/streaming/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/streaming/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/streaming/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/streaming/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/streaming/src/Core/RawClient.php delete mode 100644 seed/php-sdk/streaming/src/Core/SerializableType.php create mode 100644 seed/php-sdk/streaming/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/streaming/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/streaming/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/streaming/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/streaming/src/Core/Union.php delete mode 100644 seed/php-sdk/streaming/src/Core/Utils.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/streaming/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/trace/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/trace/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/trace/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/trace/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/trace/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/trace/src/Core/Constant.php delete mode 100644 seed/php-sdk/trace/src/Core/DateType.php delete mode 100644 seed/php-sdk/trace/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/trace/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/trace/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/trace/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/trace/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/trace/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/trace/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/trace/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/trace/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/trace/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/trace/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/trace/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/trace/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/trace/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/trace/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/trace/src/Core/RawClient.php delete mode 100644 seed/php-sdk/trace/src/Core/SerializableType.php create mode 100644 seed/php-sdk/trace/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/trace/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/trace/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/trace/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/trace/src/Core/Union.php delete mode 100644 seed/php-sdk/trace/src/Core/Utils.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/trace/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Constant.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/DateType.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/RawClient.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/SerializableType.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Union.php delete mode 100644 seed/php-sdk/undiscriminated-unions/src/Core/Utils.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/unions/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/unions/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/unions/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/unions/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/unions/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/unions/src/Core/Constant.php delete mode 100644 seed/php-sdk/unions/src/Core/DateType.php delete mode 100644 seed/php-sdk/unions/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/unions/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/unions/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/unions/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/unions/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/unions/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/unions/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/unions/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/unions/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/unions/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/unions/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/unions/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/unions/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/unions/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/unions/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/unions/src/Core/RawClient.php delete mode 100644 seed/php-sdk/unions/src/Core/SerializableType.php create mode 100644 seed/php-sdk/unions/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/unions/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/unions/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/unions/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/unions/src/Core/Union.php delete mode 100644 seed/php-sdk/unions/src/Core/Utils.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/unions/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/unknown/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/unknown/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/unknown/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/unknown/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/unknown/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/unknown/src/Core/Constant.php delete mode 100644 seed/php-sdk/unknown/src/Core/DateType.php delete mode 100644 seed/php-sdk/unknown/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/unknown/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/unknown/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/unknown/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/unknown/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/unknown/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/unknown/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/unknown/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/unknown/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/unknown/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/unknown/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/unknown/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/unknown/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/unknown/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/unknown/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/unknown/src/Core/RawClient.php delete mode 100644 seed/php-sdk/unknown/src/Core/SerializableType.php create mode 100644 seed/php-sdk/unknown/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/unknown/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/unknown/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/unknown/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/unknown/src/Core/Union.php delete mode 100644 seed/php-sdk/unknown/src/Core/Utils.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/unknown/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/validation/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/validation/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/validation/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/validation/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/validation/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/validation/src/Core/Constant.php delete mode 100644 seed/php-sdk/validation/src/Core/DateType.php delete mode 100644 seed/php-sdk/validation/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/validation/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/validation/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/validation/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/validation/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/validation/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/validation/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/validation/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/validation/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/validation/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/validation/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/validation/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/validation/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/validation/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/validation/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/validation/src/Core/RawClient.php delete mode 100644 seed/php-sdk/validation/src/Core/SerializableType.php create mode 100644 seed/php-sdk/validation/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/validation/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/validation/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/validation/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/validation/src/Core/Union.php delete mode 100644 seed/php-sdk/validation/src/Core/Utils.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/validation/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/variables/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/variables/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/variables/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/variables/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/variables/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/variables/src/Core/Constant.php delete mode 100644 seed/php-sdk/variables/src/Core/DateType.php delete mode 100644 seed/php-sdk/variables/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/variables/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/variables/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/variables/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/variables/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/variables/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/variables/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/variables/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/variables/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/variables/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/variables/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/variables/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/variables/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/variables/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/variables/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/variables/src/Core/RawClient.php delete mode 100644 seed/php-sdk/variables/src/Core/SerializableType.php create mode 100644 seed/php-sdk/variables/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/variables/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/variables/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/variables/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/variables/src/Core/Union.php delete mode 100644 seed/php-sdk/variables/src/Core/Utils.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/variables/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/Constant.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/DateType.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/RawClient.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/SerializableType.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/version-no-default/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/Union.php delete mode 100644 seed/php-sdk/version-no-default/src/Core/Utils.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/version/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/version/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/version/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/version/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/version/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/version/src/Core/Constant.php delete mode 100644 seed/php-sdk/version/src/Core/DateType.php delete mode 100644 seed/php-sdk/version/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/version/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/version/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/version/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/version/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/version/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/version/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/version/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/version/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/version/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/version/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/version/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/version/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/version/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/version/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/version/src/Core/RawClient.php delete mode 100644 seed/php-sdk/version/src/Core/SerializableType.php create mode 100644 seed/php-sdk/version/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/version/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/version/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/version/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/version/src/Core/Union.php delete mode 100644 seed/php-sdk/version/src/Core/Utils.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/version/tests/Seed/Core/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/websocket/src/Core/ArrayType.php delete mode 100644 seed/php-sdk/websocket/src/Core/BaseApiRequest.php create mode 100644 seed/php-sdk/websocket/src/Core/Client/BaseApiRequest.php create mode 100644 seed/php-sdk/websocket/src/Core/Client/HttpMethod.php create mode 100644 seed/php-sdk/websocket/src/Core/Client/RawClient.php delete mode 100644 seed/php-sdk/websocket/src/Core/Constant.php delete mode 100644 seed/php-sdk/websocket/src/Core/DateType.php delete mode 100644 seed/php-sdk/websocket/src/Core/HttpMethod.php create mode 100644 seed/php-sdk/websocket/src/Core/Json/JsonApiRequest.php create mode 100644 seed/php-sdk/websocket/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-sdk/websocket/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-sdk/websocket/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-sdk/websocket/src/Core/Json/JsonProperty.php create mode 100644 seed/php-sdk/websocket/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-sdk/websocket/src/Core/Json/SerializableType.php create mode 100644 seed/php-sdk/websocket/src/Core/Json/Utils.php delete mode 100644 seed/php-sdk/websocket/src/Core/JsonApiRequest.php delete mode 100644 seed/php-sdk/websocket/src/Core/JsonDecoder.php delete mode 100644 seed/php-sdk/websocket/src/Core/JsonDeserializer.php delete mode 100644 seed/php-sdk/websocket/src/Core/JsonEncoder.php delete mode 100644 seed/php-sdk/websocket/src/Core/JsonProperty.php delete mode 100644 seed/php-sdk/websocket/src/Core/JsonSerializer.php delete mode 100644 seed/php-sdk/websocket/src/Core/RawClient.php delete mode 100644 seed/php-sdk/websocket/src/Core/SerializableType.php create mode 100644 seed/php-sdk/websocket/src/Core/Types/ArrayType.php create mode 100644 seed/php-sdk/websocket/src/Core/Types/Constant.php create mode 100644 seed/php-sdk/websocket/src/Core/Types/DateType.php create mode 100644 seed/php-sdk/websocket/src/Core/Types/Union.php delete mode 100644 seed/php-sdk/websocket/src/Core/Union.php delete mode 100644 seed/php-sdk/websocket/src/Core/Utils.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Client/RawClientTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/DateArrayTypeTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/EmptyArraysTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/EnumTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/InvalidTypesTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/DateArrayTypeTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/EmptyArraysTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/InvalidTypesTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/MixedDateArrayTypeTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/NullPropertyTypeTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/NullableArrayTypeTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/ScalarTypesTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/TestTypeTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/UnionArrayTypeTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/UnionPropertyTypeTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/MixedDateArrayTypeTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/NestedUnionArrayTypeTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/NullPropertyTypeTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/NullableArrayTypeTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/RawClientTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/ScalarTypesTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/TestTypeTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/UnionArrayTypeTest.php delete mode 100644 seed/php-sdk/websocket/tests/Seed/Core/UnionPropertyTypeTest.php diff --git a/generators/commons/src/project/File.ts b/generators/commons/src/project/File.ts index a0931e2e6ad..906e48f6add 100644 --- a/generators/commons/src/project/File.ts +++ b/generators/commons/src/project/File.ts @@ -1,5 +1,6 @@ import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; import { mkdir, writeFile } from "fs/promises"; +import path from "path"; export class File { public filename: string; @@ -13,8 +14,8 @@ export class File { } public async write(directoryPrefix: AbsoluteFilePath): Promise { - const outputDirectory = join(directoryPrefix, this.directory); - await mkdir(outputDirectory, { recursive: true }); - await writeFile(`${outputDirectory}/${this.filename}`, this.fileContents); + const filepath = `${join(directoryPrefix, this.directory)}/${this.filename}`; + await mkdir(path.dirname(filepath), { recursive: true }); + await writeFile(filepath, this.fileContents); } } diff --git a/generators/php/codegen/src/AsIs.ts b/generators/php/codegen/src/AsIs.ts index afe44fb2275..648935c3bdb 100644 --- a/generators/php/codegen/src/AsIs.ts +++ b/generators/php/codegen/src/AsIs.ts @@ -1,35 +1,43 @@ export enum AsIsFiles { - BaseApiRequest = "BaseApiRequest.Template.php", - ClientOptions = "ClientOptions.Template.php", - GitIgnore = ".gitignore", + // Top-level files. GithubCiYml = "github-ci.yml", - HttpMethod = "HttpMethod.Template.php", - JsonApiRequest = "JsonApiRequest.Template.php", + GitIgnore = ".gitignore", PhpStanNeon = "phpstan.neon", PhpUnitXml = "phpunit.xml", - RawClient = "RawClient.Template.php", - RawClientTest = "RawClientTest.Template.php", - DateArrayTypeTest = "DateArrayTypeTest.Template.php", - EmptyArraysTest = "EmptyArraysTest.Template.php", - InvalidTypesTest = "InvalidTypesTest.Template.php", - MixedDateArrayTypeTest = "MixedDateArrayTypeTest.Template.php", - NestedUnionArrayTypeTest = "NestedUnionArrayTypeTest.Template.php", - NullableArrayTypeTest = "NullableArrayTypeTest.Template.php", - NullPropertyTypeTest = "NullPropertyTypeTest.Template.php", - UnionPropertyTypeTest = "UnionPropertyTypeTest.Template.php", - ScalarTypesTest = "ScalarTypesTest.Template.php", - EnumTest = "EnumTest.Template.php", - TestTypeTest = "TestTypeTest.Template.php", - UnionArrayTypeTest = "UnionArrayTypeTest.Template.php", - ArrayType = "ArrayType.Template.php", - Constant = "Constant.Template.php", - DateType = "DateType.Template.php", - JsonProperty = "JsonProperty.Template.php", - SerializableType = "SerializableType.Template.php", - Union = "Union.Template.php", - JsonDecoder = "JsonDecoder.Template.php", - JsonDeserializer = "JsonDeserializer.Template.php", - JsonEncoder = "JsonEncoder.Template.php", - JsonSerializer = "JsonSerializer.Template.php", - Utils = "Utils.Template.php" + + // Core/Client files. + BaseApiRequest = "Client/BaseApiRequest.Template.php", + HttpMethod = "Client/HttpMethod.Template.php", + RawClient = "Client/RawClient.Template.php", + RawClientTest = "Client/RawClientTest.Template.php", + + // Core/Json files. + JsonApiRequest = "Json/JsonApiRequest.Template.php", + JsonDecoder = "Json/JsonDecoder.Template.php", + JsonDeserializer = "Json/JsonDeserializer.Template.php", + JsonEncoder = "Json/JsonEncoder.Template.php", + JsonProperty = "Json/JsonProperty.Template.php", + JsonSerializer = "Json/JsonSerializer.Template.php", + SerializableType = "Json/SerializableType.Template.php", + Utils = "Json/Utils.Template.php", + + // Tests/Core/Json files. + DateArrayTypeTest = "Json/DateArrayTypeTest.Template.php", + EmptyArraysTest = "Json/EmptyArraysTest.Template.php", + EnumTest = "Json/EnumTest.Template.php", + InvalidTypesTest = "Json/InvalidTypesTest.Template.php", + MixedDateArrayTypeTest = "Json/MixedDateArrayTypeTest.Template.php", + NestedUnionArrayTypeTest = "Json/NestedUnionArrayTypeTest.Template.php", + NullableArrayTypeTest = "Json/NullableArrayTypeTest.Template.php", + NullPropertyTypeTest = "Json/NullPropertyTypeTest.Template.php", + ScalarTypesTest = "Json/ScalarTypesTest.Template.php", + TestTypeTest = "Json/TestTypeTest.Template.php", + UnionArrayTypeTest = "Json/UnionArrayTypeTest.Template.php", + UnionPropertyTypeTest = "Json/UnionPropertyTypeTest.Template.php", + + // Core/Types files. + ArrayType = "Types/ArrayType.Template.php", + Constant = "Types/Constant.Template.php", + DateType = "Types/DateType.Template.php", + Union = "Types/Union.Template.php" } diff --git a/generators/php/codegen/src/asIs/BaseApiRequest.Template.php b/generators/php/codegen/src/asIs/Client/BaseApiRequest.Template.php similarity index 100% rename from generators/php/codegen/src/asIs/BaseApiRequest.Template.php rename to generators/php/codegen/src/asIs/Client/BaseApiRequest.Template.php diff --git a/generators/php/codegen/src/asIs/HttpMethod.Template.php b/generators/php/codegen/src/asIs/Client/HttpMethod.Template.php similarity index 100% rename from generators/php/codegen/src/asIs/HttpMethod.Template.php rename to generators/php/codegen/src/asIs/Client/HttpMethod.Template.php diff --git a/generators/php/codegen/src/asIs/RawClient.Template.php b/generators/php/codegen/src/asIs/Client/RawClient.Template.php similarity index 98% rename from generators/php/codegen/src/asIs/RawClient.Template.php rename to generators/php/codegen/src/asIs/Client/RawClient.Template.php index 02bdbe6afd0..aad4f182dd6 100644 --- a/generators/php/codegen/src/asIs/RawClient.Template.php +++ b/generators/php/codegen/src/asIs/Client/RawClient.Template.php @@ -10,6 +10,7 @@ use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; +use <%= coreNamespace%>\Json\JsonApiRequest; class RawClient { diff --git a/generators/php/codegen/src/asIs/RawClientTest.Template.php b/generators/php/codegen/src/asIs/Client/RawClientTest.Template.php similarity index 94% rename from generators/php/codegen/src/asIs/RawClientTest.Template.php rename to generators/php/codegen/src/asIs/Client/RawClientTest.Template.php index 811693af976..ccd072df7ae 100644 --- a/generators/php/codegen/src/asIs/RawClientTest.Template.php +++ b/generators/php/codegen/src/asIs/Client/RawClientTest.Template.php @@ -8,10 +8,10 @@ use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; -use <%= coreNamespace%>\BaseApiRequest; -use <%= coreNamespace%>\JsonApiRequest; -use <%= coreNamespace%>\HttpMethod; -use <%= coreNamespace%>\RawClient; +use <%= coreNamespace%>\Client\BaseApiRequest; +use <%= coreNamespace%>\Client\HttpMethod; +use <%= coreNamespace%>\Client\RawClient; +use <%= coreNamespace%>\Json\JsonApiRequest; class RawClientTest extends TestCase diff --git a/generators/php/codegen/src/asIs/DateArrayTypeTest.Template.php b/generators/php/codegen/src/asIs/Json/DateArrayTypeTest.Template.php similarity index 92% rename from generators/php/codegen/src/asIs/DateArrayTypeTest.Template.php rename to generators/php/codegen/src/asIs/Json/DateArrayTypeTest.Template.php index ca53121351f..032245709b4 100644 --- a/generators/php/codegen/src/asIs/DateArrayTypeTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/DateArrayTypeTest.Template.php @@ -2,11 +2,11 @@ namespace <%= namespace%>; -use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\ArrayType; use DateTime; +use PHPUnit\Framework\TestCase; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; class DateArrayType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/EmptyArraysTest.Template.php b/generators/php/codegen/src/asIs/Json/EmptyArraysTest.Template.php similarity index 92% rename from generators/php/codegen/src/asIs/EmptyArraysTest.Template.php rename to generators/php/codegen/src/asIs/Json/EmptyArraysTest.Template.php index 467da550246..c443c60d1d1 100644 --- a/generators/php/codegen/src/asIs/EmptyArraysTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/EmptyArraysTest.Template.php @@ -3,10 +3,10 @@ namespace <%= namespace%>; use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\ArrayType; -use <%= coreNamespace%>\Union; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; +use <%= coreNamespace%>\Types\Union; class EmptyArraysType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/EnumTest.Template.php b/generators/php/codegen/src/asIs/Json/EnumTest.Template.php similarity index 91% rename from generators/php/codegen/src/asIs/EnumTest.Template.php rename to generators/php/codegen/src/asIs/Json/EnumTest.Template.php index 13b666e58a8..dea5b5252db 100644 --- a/generators/php/codegen/src/asIs/EnumTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/EnumTest.Template.php @@ -4,9 +4,9 @@ use JsonSerializable; use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\ArrayType; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; enum Shape: string implements JsonSerializable { diff --git a/generators/php/codegen/src/asIs/InvalidTypesTest.Template.php b/generators/php/codegen/src/asIs/Json/InvalidTypesTest.Template.php similarity index 91% rename from generators/php/codegen/src/asIs/InvalidTypesTest.Template.php rename to generators/php/codegen/src/asIs/Json/InvalidTypesTest.Template.php index 6c05c3a4bf6..24fd1fbb1c1 100644 --- a/generators/php/codegen/src/asIs/InvalidTypesTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/InvalidTypesTest.Template.php @@ -3,8 +3,8 @@ namespace <%= namespace%>; use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Json\JsonProperty; class InvalidType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/JsonApiRequest.Template.php b/generators/php/codegen/src/asIs/Json/JsonApiRequest.Template.php similarity index 89% rename from generators/php/codegen/src/asIs/JsonApiRequest.Template.php rename to generators/php/codegen/src/asIs/Json/JsonApiRequest.Template.php index 2f74ce3494f..41aa06f0a69 100644 --- a/generators/php/codegen/src/asIs/JsonApiRequest.Template.php +++ b/generators/php/codegen/src/asIs/Json/JsonApiRequest.Template.php @@ -1,6 +1,8 @@ ; +use <%= coreNamespace%>\Client\BaseApiRequest; +use <%= coreNamespace%>\Client\HttpMethod; class JsonApiRequest extends BaseApiRequest { diff --git a/generators/php/codegen/src/asIs/JsonDecoder.Template.php b/generators/php/codegen/src/asIs/Json/JsonDecoder.Template.php similarity index 99% rename from generators/php/codegen/src/asIs/JsonDecoder.Template.php rename to generators/php/codegen/src/asIs/Json/JsonDecoder.Template.php index e34af48812a..6662a733a39 100644 --- a/generators/php/codegen/src/asIs/JsonDecoder.Template.php +++ b/generators/php/codegen/src/asIs/Json/JsonDecoder.Template.php @@ -5,6 +5,7 @@ use DateTime; use Exception; use JsonException; +use <%= coreNamespace%>\Types\Union; class JsonDecoder { diff --git a/generators/php/codegen/src/asIs/JsonDeserializer.Template.php b/generators/php/codegen/src/asIs/Json/JsonDeserializer.Template.php similarity index 98% rename from generators/php/codegen/src/asIs/JsonDeserializer.Template.php rename to generators/php/codegen/src/asIs/Json/JsonDeserializer.Template.php index f0a42290111..9e290b6c30e 100644 --- a/generators/php/codegen/src/asIs/JsonDeserializer.Template.php +++ b/generators/php/codegen/src/asIs/Json/JsonDeserializer.Template.php @@ -1,10 +1,12 @@ ; +namespace <%= namespace%>; use DateTime; use Exception; use JsonException; +use <%= coreNamespace%>\Types\Constant; +use <%= coreNamespace%>\Types\Union; class JsonDeserializer { diff --git a/generators/php/codegen/src/asIs/JsonEncoder.Template.php b/generators/php/codegen/src/asIs/Json/JsonEncoder.Template.php similarity index 100% rename from generators/php/codegen/src/asIs/JsonEncoder.Template.php rename to generators/php/codegen/src/asIs/Json/JsonEncoder.Template.php diff --git a/generators/php/codegen/src/asIs/JsonProperty.Template.php b/generators/php/codegen/src/asIs/Json/JsonProperty.Template.php similarity index 100% rename from generators/php/codegen/src/asIs/JsonProperty.Template.php rename to generators/php/codegen/src/asIs/Json/JsonProperty.Template.php diff --git a/generators/php/codegen/src/asIs/JsonSerializer.Template.php b/generators/php/codegen/src/asIs/Json/JsonSerializer.Template.php similarity index 98% rename from generators/php/codegen/src/asIs/JsonSerializer.Template.php rename to generators/php/codegen/src/asIs/Json/JsonSerializer.Template.php index bd046196807..9b33c9fdef4 100644 --- a/generators/php/codegen/src/asIs/JsonSerializer.Template.php +++ b/generators/php/codegen/src/asIs/Json/JsonSerializer.Template.php @@ -1,11 +1,13 @@ ; +namespace <%= namespace%>; use DateTime; use Exception; use JsonException; use JsonSerializable; +use <%= coreNamespace%>\Types\Constant; +use <%= coreNamespace%>\Types\Union; class JsonSerializer { diff --git a/generators/php/codegen/src/asIs/MixedDateArrayTypeTest.Template.php b/generators/php/codegen/src/asIs/Json/MixedDateArrayTypeTest.Template.php similarity index 90% rename from generators/php/codegen/src/asIs/MixedDateArrayTypeTest.Template.php rename to generators/php/codegen/src/asIs/Json/MixedDateArrayTypeTest.Template.php index cf0dc64a1fb..326c85a912c 100644 --- a/generators/php/codegen/src/asIs/MixedDateArrayTypeTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/MixedDateArrayTypeTest.Template.php @@ -2,12 +2,12 @@ namespace <%= namespace%>; -use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\ArrayType; -use <%= coreNamespace%>\Union; use DateTime; +use PHPUnit\Framework\TestCase; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; +use <%= coreNamespace%>\Types\Union; class MixedDateArrayType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/NestedUnionArrayTypeTest.Template.php b/generators/php/codegen/src/asIs/Json/NestedUnionArrayTypeTest.Template.php similarity index 93% rename from generators/php/codegen/src/asIs/NestedUnionArrayTypeTest.Template.php rename to generators/php/codegen/src/asIs/Json/NestedUnionArrayTypeTest.Template.php index b5b5016976d..1d6babfdf20 100644 --- a/generators/php/codegen/src/asIs/NestedUnionArrayTypeTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/NestedUnionArrayTypeTest.Template.php @@ -2,13 +2,13 @@ namespace <%= namespace%>; -use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\Constant; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\ArrayType; -use <%= coreNamespace%>\Union; use DateTime; +use PHPUnit\Framework\TestCase; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; +use <%= coreNamespace%>\Types\Constant; +use <%= coreNamespace%>\Types\Union; // Supporting Classes diff --git a/generators/php/codegen/src/asIs/NullPropertyTypeTest.Template.php b/generators/php/codegen/src/asIs/Json/NullPropertyTypeTest.Template.php similarity index 93% rename from generators/php/codegen/src/asIs/NullPropertyTypeTest.Template.php rename to generators/php/codegen/src/asIs/Json/NullPropertyTypeTest.Template.php index 46ff130f7ee..fc77d694790 100644 --- a/generators/php/codegen/src/asIs/NullPropertyTypeTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/NullPropertyTypeTest.Template.php @@ -3,8 +3,8 @@ namespace <%= namespace%>; use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; class NullPropertyType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/NullableArrayTypeTest.Template.php b/generators/php/codegen/src/asIs/Json/NullableArrayTypeTest.Template.php similarity index 87% rename from generators/php/codegen/src/asIs/NullableArrayTypeTest.Template.php rename to generators/php/codegen/src/asIs/Json/NullableArrayTypeTest.Template.php index f1a2d60963c..967a404fc54 100644 --- a/generators/php/codegen/src/asIs/NullableArrayTypeTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/NullableArrayTypeTest.Template.php @@ -3,10 +3,10 @@ namespace <%= namespace%>; use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\ArrayType; -use <%= coreNamespace%>\Union; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; +use <%= coreNamespace%>\Types\Union; class NullableArrayType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/ScalarTypesTest.Template.php b/generators/php/codegen/src/asIs/Json/ScalarTypesTest.Template.php similarity index 95% rename from generators/php/codegen/src/asIs/ScalarTypesTest.Template.php rename to generators/php/codegen/src/asIs/Json/ScalarTypesTest.Template.php index f1a12d00ee0..f563a982d1c 100644 --- a/generators/php/codegen/src/asIs/ScalarTypesTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/ScalarTypesTest.Template.php @@ -3,10 +3,10 @@ namespace <%= namespace%>; use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\ArrayType; -use <%= coreNamespace%>\Union; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; +use <%= coreNamespace%>\Types\Union; class ScalarTypesTestType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/SerializableType.Template.php b/generators/php/codegen/src/asIs/Json/SerializableType.Template.php similarity index 97% rename from generators/php/codegen/src/asIs/SerializableType.Template.php rename to generators/php/codegen/src/asIs/Json/SerializableType.Template.php index 6abe274c4c2..c70cc6c0d29 100644 --- a/generators/php/codegen/src/asIs/SerializableType.Template.php +++ b/generators/php/codegen/src/asIs/Json/SerializableType.Template.php @@ -1,12 +1,15 @@ ; +namespace <%= namespace%>; use DateTime; use Exception; use JsonException; use ReflectionNamedType; use ReflectionProperty; +use <%= coreNamespace%>\Types\ArrayType; +use <%= coreNamespace%>\Types\DateType; +use <%= coreNamespace%>\Types\Union; /** * Provides generic serialization and deserialization methods. diff --git a/generators/php/codegen/src/asIs/TestTypeTest.Template.php b/generators/php/codegen/src/asIs/Json/TestTypeTest.Template.php similarity index 97% rename from generators/php/codegen/src/asIs/TestTypeTest.Template.php rename to generators/php/codegen/src/asIs/Json/TestTypeTest.Template.php index 659a70973c9..8dc94108c6c 100644 --- a/generators/php/codegen/src/asIs/TestTypeTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/TestTypeTest.Template.php @@ -2,13 +2,13 @@ namespace <%= namespace%>; -use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\DateType; -use <%= coreNamespace%>\ArrayType; use DateTime; -use <%= coreNamespace%>\Union; +use PHPUnit\Framework\TestCase; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; +use <%= coreNamespace%>\Types\DateType; +use <%= coreNamespace%>\Types\Union; class TestNestedType1 extends SerializableType { diff --git a/generators/php/codegen/src/asIs/UnionArrayTypeTest.Template.php b/generators/php/codegen/src/asIs/Json/UnionArrayTypeTest.Template.php similarity index 89% rename from generators/php/codegen/src/asIs/UnionArrayTypeTest.Template.php rename to generators/php/codegen/src/asIs/Json/UnionArrayTypeTest.Template.php index 95ada8b638d..16c4ec7decb 100644 --- a/generators/php/codegen/src/asIs/UnionArrayTypeTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/UnionArrayTypeTest.Template.php @@ -3,10 +3,10 @@ namespace <%= namespace%>; use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\ArrayType; -use <%= coreNamespace%>\Union; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\ArrayType; +use <%= coreNamespace%>\Types\Union; class UnionArrayType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/UnionPropertyTypeTest.Template.php b/generators/php/codegen/src/asIs/Json/UnionPropertyTypeTest.Template.php similarity index 96% rename from generators/php/codegen/src/asIs/UnionPropertyTypeTest.Template.php rename to generators/php/codegen/src/asIs/Json/UnionPropertyTypeTest.Template.php index 4377a6fee9b..f5e0ebc6e01 100644 --- a/generators/php/codegen/src/asIs/UnionPropertyTypeTest.Template.php +++ b/generators/php/codegen/src/asIs/Json/UnionPropertyTypeTest.Template.php @@ -3,9 +3,9 @@ namespace <%= namespace%>; use PHPUnit\Framework\TestCase; -use <%= coreNamespace%>\JsonProperty; -use <%= coreNamespace%>\SerializableType; -use <%= coreNamespace%>\Union; +use <%= coreNamespace%>\Json\JsonProperty; +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Types\Union; class UnionPropertyType extends SerializableType { diff --git a/generators/php/codegen/src/asIs/Utils.Template.php b/generators/php/codegen/src/asIs/Json/Utils.Template.php similarity index 98% rename from generators/php/codegen/src/asIs/Utils.Template.php rename to generators/php/codegen/src/asIs/Json/Utils.Template.php index 243b4286952..aef34595e9e 100644 --- a/generators/php/codegen/src/asIs/Utils.Template.php +++ b/generators/php/codegen/src/asIs/Json/Utils.Template.php @@ -1,6 +1,6 @@ ; +namespace <%= namespace%>; use DateTime; use Exception; diff --git a/generators/php/codegen/src/asIs/ArrayType.Template.php b/generators/php/codegen/src/asIs/Types/ArrayType.Template.php similarity index 100% rename from generators/php/codegen/src/asIs/ArrayType.Template.php rename to generators/php/codegen/src/asIs/Types/ArrayType.Template.php diff --git a/generators/php/codegen/src/asIs/Constant.Template.php b/generators/php/codegen/src/asIs/Types/Constant.Template.php similarity index 100% rename from generators/php/codegen/src/asIs/Constant.Template.php rename to generators/php/codegen/src/asIs/Types/Constant.Template.php diff --git a/generators/php/codegen/src/asIs/DateType.Template.php b/generators/php/codegen/src/asIs/Types/DateType.Template.php similarity index 100% rename from generators/php/codegen/src/asIs/DateType.Template.php rename to generators/php/codegen/src/asIs/Types/DateType.Template.php diff --git a/generators/php/codegen/src/asIs/Union.Template.php b/generators/php/codegen/src/asIs/Types/Union.Template.php similarity index 98% rename from generators/php/codegen/src/asIs/Union.Template.php rename to generators/php/codegen/src/asIs/Types/Union.Template.php index 78aa129b4cc..c47277a4a00 100644 --- a/generators/php/codegen/src/asIs/Union.Template.php +++ b/generators/php/codegen/src/asIs/Types/Union.Template.php @@ -1,6 +1,6 @@ ; +namespace <%= namespace%>; use Attribute; diff --git a/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts b/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts index d8678d182d1..12460deb336 100644 --- a/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts +++ b/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts @@ -92,10 +92,34 @@ export abstract class AbstractPhpGeneratorContext< return `${this.rootNamespace}\\Core`; } + public getCoreClientNamespace(): string { + return `${this.getCoreNamespace()}\\Client`; + } + + public getCoreJsonNamespace(): string { + return `${this.getCoreNamespace()}\\Json`; + } + + public getCoreTypesNamespace(): string { + return `${this.getCoreNamespace()}\\Types`; + } + public getCoreTestsNamespace(): string { return `${this.rootNamespace}\\Tests\\Core`; } + public getCoreClientTestsNamespace(): string { + return `${this.getCoreTestsNamespace()}\\Client`; + } + + public getCoreJsonTestsNamespace(): string { + return `${this.getCoreTestsNamespace()}\\Json`; + } + + public getCoreTypesTestsNamespace(): string { + return `${this.getCoreTestsNamespace()}\\Types`; + } + public getParameterName(name: Name): string { return this.prependUnderscoreIfNeeded(name.camelCase.unsafeName); } @@ -124,33 +148,47 @@ export abstract class AbstractPhpGeneratorContext< } public getDateTypeAttributeClassReference(): php.ClassReference { - return this.getCoreClassReference("DateType"); + return this.getCoreTypesClassReference("DateType"); } public getConstantClassReference(): php.ClassReference { - return this.getCoreClassReference("Constant"); + return this.getCoreTypesClassReference("Constant"); } public getJsonPropertyAttributeClassReference(): php.ClassReference { - return this.getCoreClassReference("JsonProperty"); + return this.getCoreJsonClassReference("JsonProperty"); } public getSerializableTypeClassReference(): php.ClassReference { - return this.getCoreClassReference("SerializableType"); + return this.getCoreJsonClassReference("SerializableType"); } public getUnionClassReference(): php.ClassReference { - return this.getCoreClassReference("Union"); + return this.getCoreTypesClassReference("Union"); } public getArrayTypeClassReference(): php.ClassReference { - return this.getCoreClassReference("ArrayType"); + return this.getCoreTypesClassReference("ArrayType"); + } + + public getCoreClientClassReference(name: string): php.ClassReference { + return php.classReference({ + name, + namespace: this.getCoreClientNamespace() + }); + } + + public getCoreJsonClassReference(name: string): php.ClassReference { + return php.classReference({ + name, + namespace: this.getCoreJsonNamespace() + }); } - public getCoreClassReference(name: string): php.ClassReference { + public getCoreTypesClassReference(name: string): php.ClassReference { return php.classReference({ name, - namespace: this.getCoreNamespace() + namespace: this.getCoreTypesNamespace() }); } diff --git a/generators/php/codegen/src/project/PhpProject.ts b/generators/php/codegen/src/project/PhpProject.ts index 77de564d543..b02d6cc0765 100644 --- a/generators/php/codegen/src/project/PhpProject.ts +++ b/generators/php/codegen/src/project/PhpProject.ts @@ -94,12 +94,13 @@ export class PhpProject extends AbstractProject { const contents = (await readFile(getAsIsFilepath(filename))).toString(); + return new File( filename.replace(".Template", ""), RelativeFilePath.of(""), this.replaceTemplate({ contents, - namespace + namespace: this.getNestedNamespace({ namespace, filename }) }) ); } @@ -159,6 +160,14 @@ export class PhpProject extends AbstractProject 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/alias-extends/src/Core/Constant.php b/seed/php-model/alias-extends/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/alias-extends/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/alias-extends/src/Core/Json/JsonDeserializer.php b/seed/php-model/alias-extends/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/alias-extends/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/alias-extends/src/Core/Json/JsonEncoder.php b/seed/php-model/alias-extends/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/alias-extends/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/alias-extends/src/Core/Json/SerializableType.php b/seed/php-model/alias-extends/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/alias-extends/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/alias-extends/src/Core/Json/Utils.php b/seed/php-model/alias-extends/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/alias-extends/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/alias-extends/src/Core/JsonDecoder.php b/seed/php-model/alias-extends/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/alias-extends/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/alias-extends/src/Core/JsonDeserializer.php b/seed/php-model/alias-extends/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/alias-extends/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/alias-extends/src/Core/JsonEncoder.php b/seed/php-model/alias-extends/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/alias-extends/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/alias-extends/src/Core/SerializableType.php b/seed/php-model/alias-extends/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/alias-extends/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/alias-extends/src/Core/Types/ArrayType.php b/seed/php-model/alias-extends/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/alias-extends/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/alias-extends/src/Core/Types/Constant.php b/seed/php-model/alias-extends/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/alias-extends/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/alias-extends/src/Core/Union.php b/seed/php-model/alias-extends/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/alias-extends/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/alias-extends/src/Core/Utils.php b/seed/php-model/alias-extends/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/alias-extends/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/alias-extends/src/Parent_.php b/seed/php-model/alias-extends/src/Parent_.php index f1318948cb8..7aaf49b5961 100644 --- a/seed/php-model/alias-extends/src/Parent_.php +++ b/seed/php-model/alias-extends/src/Parent_.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Parent_ extends SerializableType { diff --git a/seed/php-model/alias-extends/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/alias-extends/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/EnumTest.php b/seed/php-model/alias-extends/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/alias-extends/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/alias-extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/alias-extends/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/TestTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/alias/src/Core/ArrayType.php b/seed/php-model/alias/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/alias/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/alias/src/Core/Constant.php b/seed/php-model/alias/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/alias/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/alias/src/Core/Json/JsonDeserializer.php b/seed/php-model/alias/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/alias/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/alias/src/Core/Json/JsonEncoder.php b/seed/php-model/alias/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/alias/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/alias/src/Core/Json/SerializableType.php b/seed/php-model/alias/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/alias/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/alias/src/Core/Json/Utils.php b/seed/php-model/alias/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/alias/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/alias/src/Core/JsonDecoder.php b/seed/php-model/alias/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/alias/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/alias/src/Core/JsonDeserializer.php b/seed/php-model/alias/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/alias/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/alias/src/Core/JsonEncoder.php b/seed/php-model/alias/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/alias/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/alias/src/Core/SerializableType.php b/seed/php-model/alias/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/alias/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/alias/src/Core/Types/ArrayType.php b/seed/php-model/alias/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/alias/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/alias/src/Core/Types/Constant.php b/seed/php-model/alias/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/alias/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/alias/src/Core/Union.php b/seed/php-model/alias/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/alias/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/alias/src/Core/Utils.php b/seed/php-model/alias/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/alias/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/alias/src/Type.php b/seed/php-model/alias/src/Type.php index 9fac2717ce8..3b88fee3fd9 100644 --- a/seed/php-model/alias/src/Type.php +++ b/seed/php-model/alias/src/Type.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * A simple type with just a name. diff --git a/seed/php-model/alias/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/alias/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/EnumTest.php b/seed/php-model/alias/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/alias/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/alias/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/alias/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/alias/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/alias/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/alias/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/alias/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/alias/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/alias/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/alias/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/TestTypeTest.php b/seed/php-model/alias/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/alias/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/alias/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/alias/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/alias/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/any-auth/src/Auth/TokenResponse.php b/seed/php-model/any-auth/src/Auth/TokenResponse.php index 5f67f246612..8f67067fe38 100644 --- a/seed/php-model/any-auth/src/Auth/TokenResponse.php +++ b/seed/php-model/any-auth/src/Auth/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-model/any-auth/src/Core/ArrayType.php b/seed/php-model/any-auth/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/any-auth/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/any-auth/src/Core/Constant.php b/seed/php-model/any-auth/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/any-auth/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/any-auth/src/Core/Json/JsonDeserializer.php b/seed/php-model/any-auth/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/any-auth/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/any-auth/src/Core/Json/JsonEncoder.php b/seed/php-model/any-auth/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/any-auth/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/any-auth/src/Core/Json/SerializableType.php b/seed/php-model/any-auth/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/any-auth/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/any-auth/src/Core/Json/Utils.php b/seed/php-model/any-auth/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/any-auth/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/any-auth/src/Core/JsonDecoder.php b/seed/php-model/any-auth/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/any-auth/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/any-auth/src/Core/JsonDeserializer.php b/seed/php-model/any-auth/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/any-auth/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/any-auth/src/Core/JsonEncoder.php b/seed/php-model/any-auth/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/any-auth/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/any-auth/src/Core/SerializableType.php b/seed/php-model/any-auth/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/any-auth/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/any-auth/src/Core/Types/ArrayType.php b/seed/php-model/any-auth/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/any-auth/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/any-auth/src/Core/Types/Constant.php b/seed/php-model/any-auth/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/any-auth/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/any-auth/src/Core/Union.php b/seed/php-model/any-auth/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/any-auth/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/any-auth/src/Core/Utils.php b/seed/php-model/any-auth/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/any-auth/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/any-auth/src/User/User.php b/seed/php-model/any-auth/src/User/User.php index 7f38b952d59..fc815c903d5 100644 --- a/seed/php-model/any-auth/src/User/User.php +++ b/seed/php-model/any-auth/src/User/User.php @@ -2,8 +2,8 @@ namespace Seed\User; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-model/any-auth/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/any-auth/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/EnumTest.php b/seed/php-model/any-auth/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/any-auth/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/any-auth/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/TestTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/api-wide-base-path/src/Core/ArrayType.php b/seed/php-model/api-wide-base-path/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/api-wide-base-path/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/api-wide-base-path/src/Core/Constant.php b/seed/php-model/api-wide-base-path/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/api-wide-base-path/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/api-wide-base-path/src/Core/Json/JsonDeserializer.php b/seed/php-model/api-wide-base-path/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/api-wide-base-path/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/api-wide-base-path/src/Core/Json/JsonEncoder.php b/seed/php-model/api-wide-base-path/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/api-wide-base-path/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/api-wide-base-path/src/Core/Json/SerializableType.php b/seed/php-model/api-wide-base-path/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/api-wide-base-path/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/api-wide-base-path/src/Core/Json/Utils.php b/seed/php-model/api-wide-base-path/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/api-wide-base-path/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/api-wide-base-path/src/Core/JsonDecoder.php b/seed/php-model/api-wide-base-path/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/api-wide-base-path/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/api-wide-base-path/src/Core/JsonDeserializer.php b/seed/php-model/api-wide-base-path/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/api-wide-base-path/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/api-wide-base-path/src/Core/JsonEncoder.php b/seed/php-model/api-wide-base-path/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/api-wide-base-path/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/api-wide-base-path/src/Core/SerializableType.php b/seed/php-model/api-wide-base-path/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/api-wide-base-path/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/api-wide-base-path/src/Core/Types/ArrayType.php b/seed/php-model/api-wide-base-path/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/api-wide-base-path/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/api-wide-base-path/src/Core/Types/Constant.php b/seed/php-model/api-wide-base-path/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/api-wide-base-path/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/api-wide-base-path/src/Core/Union.php b/seed/php-model/api-wide-base-path/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/api-wide-base-path/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/api-wide-base-path/src/Core/Utils.php b/seed/php-model/api-wide-base-path/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/api-wide-base-path/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/EnumTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/TestTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/audiences/src/Core/ArrayType.php b/seed/php-model/audiences/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/audiences/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/audiences/src/Core/Constant.php b/seed/php-model/audiences/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/audiences/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/audiences/src/Core/Json/JsonDeserializer.php b/seed/php-model/audiences/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/audiences/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/audiences/src/Core/Json/JsonEncoder.php b/seed/php-model/audiences/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/audiences/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/audiences/src/Core/Json/SerializableType.php b/seed/php-model/audiences/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/audiences/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/audiences/src/Core/Json/Utils.php b/seed/php-model/audiences/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/audiences/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/audiences/src/Core/JsonDecoder.php b/seed/php-model/audiences/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/audiences/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/audiences/src/Core/JsonDeserializer.php b/seed/php-model/audiences/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/audiences/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/audiences/src/Core/JsonEncoder.php b/seed/php-model/audiences/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/audiences/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/audiences/src/Core/SerializableType.php b/seed/php-model/audiences/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/audiences/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/audiences/src/Core/Types/ArrayType.php b/seed/php-model/audiences/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/audiences/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/audiences/src/Core/Types/Constant.php b/seed/php-model/audiences/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/audiences/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/audiences/src/Core/Union.php b/seed/php-model/audiences/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/audiences/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/audiences/src/Core/Utils.php b/seed/php-model/audiences/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/audiences/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/audiences/src/FolderA/Service/Response.php b/seed/php-model/audiences/src/FolderA/Service/Response.php index 627505a094a..1555b3f434c 100644 --- a/seed/php-model/audiences/src/FolderA/Service/Response.php +++ b/seed/php-model/audiences/src/FolderA/Service/Response.php @@ -2,9 +2,9 @@ namespace Seed\FolderA\Service; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderB\Common\Foo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-model/audiences/src/FolderB/Common/Foo.php b/seed/php-model/audiences/src/FolderB/Common/Foo.php index 4aa2df9e209..5ab0a46ecf1 100644 --- a/seed/php-model/audiences/src/FolderB/Common/Foo.php +++ b/seed/php-model/audiences/src/FolderB/Common/Foo.php @@ -2,9 +2,9 @@ namespace Seed\FolderB\Common; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderC\Common\FolderCFoo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Foo extends SerializableType { diff --git a/seed/php-model/audiences/src/FolderC/Common/FolderCFoo.php b/seed/php-model/audiences/src/FolderC/Common/FolderCFoo.php index 3ecc1f9f934..8eec2191fff 100644 --- a/seed/php-model/audiences/src/FolderC/Common/FolderCFoo.php +++ b/seed/php-model/audiences/src/FolderC/Common/FolderCFoo.php @@ -2,8 +2,8 @@ namespace Seed\FolderC\Common; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FolderCFoo extends SerializableType { diff --git a/seed/php-model/audiences/src/FolderD/Service/Response.php b/seed/php-model/audiences/src/FolderD/Service/Response.php index 92011de8c2b..c477534bac8 100644 --- a/seed/php-model/audiences/src/FolderD/Service/Response.php +++ b/seed/php-model/audiences/src/FolderD/Service/Response.php @@ -2,8 +2,8 @@ namespace Seed\FolderD\Service; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-model/audiences/src/Foo/FilteredType.php b/seed/php-model/audiences/src/Foo/FilteredType.php index c3cc100adad..a7f114334d2 100644 --- a/seed/php-model/audiences/src/Foo/FilteredType.php +++ b/seed/php-model/audiences/src/Foo/FilteredType.php @@ -2,8 +2,8 @@ namespace Seed\Foo; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FilteredType extends SerializableType { diff --git a/seed/php-model/audiences/src/Foo/ImportingType.php b/seed/php-model/audiences/src/Foo/ImportingType.php index 803575e6042..09a46b2aa74 100644 --- a/seed/php-model/audiences/src/Foo/ImportingType.php +++ b/seed/php-model/audiences/src/Foo/ImportingType.php @@ -2,8 +2,8 @@ namespace Seed\Foo; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ImportingType extends SerializableType { diff --git a/seed/php-model/audiences/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/audiences/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/EnumTest.php b/seed/php-model/audiences/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/audiences/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/audiences/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/TestTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/audiences/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/audiences/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/audiences/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/auth-environment-variables/src/Core/ArrayType.php b/seed/php-model/auth-environment-variables/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/auth-environment-variables/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/auth-environment-variables/src/Core/Constant.php b/seed/php-model/auth-environment-variables/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/auth-environment-variables/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/auth-environment-variables/src/Core/Json/JsonDeserializer.php b/seed/php-model/auth-environment-variables/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/auth-environment-variables/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/auth-environment-variables/src/Core/Json/JsonEncoder.php b/seed/php-model/auth-environment-variables/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/auth-environment-variables/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/auth-environment-variables/src/Core/Json/SerializableType.php b/seed/php-model/auth-environment-variables/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/auth-environment-variables/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/auth-environment-variables/src/Core/Json/Utils.php b/seed/php-model/auth-environment-variables/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/auth-environment-variables/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/auth-environment-variables/src/Core/JsonDecoder.php b/seed/php-model/auth-environment-variables/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/auth-environment-variables/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/auth-environment-variables/src/Core/JsonDeserializer.php b/seed/php-model/auth-environment-variables/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/auth-environment-variables/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/auth-environment-variables/src/Core/JsonEncoder.php b/seed/php-model/auth-environment-variables/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/auth-environment-variables/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/auth-environment-variables/src/Core/SerializableType.php b/seed/php-model/auth-environment-variables/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/auth-environment-variables/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/auth-environment-variables/src/Core/Types/ArrayType.php b/seed/php-model/auth-environment-variables/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/auth-environment-variables/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/auth-environment-variables/src/Core/Types/Constant.php b/seed/php-model/auth-environment-variables/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/auth-environment-variables/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/auth-environment-variables/src/Core/Union.php b/seed/php-model/auth-environment-variables/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/auth-environment-variables/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/auth-environment-variables/src/Core/Utils.php b/seed/php-model/auth-environment-variables/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/auth-environment-variables/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/EnumTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/TestTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/ArrayType.php b/seed/php-model/basic-auth-environment-variables/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/basic-auth-environment-variables/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Constant.php b/seed/php-model/basic-auth-environment-variables/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/basic-auth-environment-variables/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonDeserializer.php b/seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonEncoder.php b/seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Json/SerializableType.php b/seed/php-model/basic-auth-environment-variables/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Json/Utils.php b/seed/php-model/basic-auth-environment-variables/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/JsonDecoder.php b/seed/php-model/basic-auth-environment-variables/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/basic-auth-environment-variables/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/JsonDeserializer.php b/seed/php-model/basic-auth-environment-variables/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/basic-auth-environment-variables/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/JsonEncoder.php b/seed/php-model/basic-auth-environment-variables/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/basic-auth-environment-variables/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/SerializableType.php b/seed/php-model/basic-auth-environment-variables/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/basic-auth-environment-variables/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Types/ArrayType.php b/seed/php-model/basic-auth-environment-variables/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Types/Constant.php b/seed/php-model/basic-auth-environment-variables/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Union.php b/seed/php-model/basic-auth-environment-variables/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/basic-auth-environment-variables/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/src/Core/Utils.php b/seed/php-model/basic-auth-environment-variables/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/basic-auth-environment-variables/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/basic-auth-environment-variables/src/Errors/UnauthorizedRequestErrorBody.php b/seed/php-model/basic-auth-environment-variables/src/Errors/UnauthorizedRequestErrorBody.php index 60fab695cf1..8f30396a292 100644 --- a/seed/php-model/basic-auth-environment-variables/src/Errors/UnauthorizedRequestErrorBody.php +++ b/seed/php-model/basic-auth-environment-variables/src/Errors/UnauthorizedRequestErrorBody.php @@ -2,8 +2,8 @@ namespace Seed\Errors; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UnauthorizedRequestErrorBody extends SerializableType { diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/EnumTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/TestTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/basic-auth/src/Core/ArrayType.php b/seed/php-model/basic-auth/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/basic-auth/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/basic-auth/src/Core/Constant.php b/seed/php-model/basic-auth/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/basic-auth/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/basic-auth/src/Core/Json/JsonDeserializer.php b/seed/php-model/basic-auth/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/basic-auth/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/basic-auth/src/Core/Json/JsonEncoder.php b/seed/php-model/basic-auth/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/basic-auth/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/basic-auth/src/Core/Json/SerializableType.php b/seed/php-model/basic-auth/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/basic-auth/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/basic-auth/src/Core/Json/Utils.php b/seed/php-model/basic-auth/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/basic-auth/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/basic-auth/src/Core/JsonDecoder.php b/seed/php-model/basic-auth/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/basic-auth/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/basic-auth/src/Core/JsonDeserializer.php b/seed/php-model/basic-auth/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/basic-auth/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/basic-auth/src/Core/JsonEncoder.php b/seed/php-model/basic-auth/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/basic-auth/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/basic-auth/src/Core/SerializableType.php b/seed/php-model/basic-auth/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/basic-auth/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/basic-auth/src/Core/Types/ArrayType.php b/seed/php-model/basic-auth/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/basic-auth/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/basic-auth/src/Core/Types/Constant.php b/seed/php-model/basic-auth/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/basic-auth/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/basic-auth/src/Core/Union.php b/seed/php-model/basic-auth/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/basic-auth/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/basic-auth/src/Core/Utils.php b/seed/php-model/basic-auth/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/basic-auth/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/basic-auth/src/Errors/UnauthorizedRequestErrorBody.php b/seed/php-model/basic-auth/src/Errors/UnauthorizedRequestErrorBody.php index 60fab695cf1..8f30396a292 100644 --- a/seed/php-model/basic-auth/src/Errors/UnauthorizedRequestErrorBody.php +++ b/seed/php-model/basic-auth/src/Errors/UnauthorizedRequestErrorBody.php @@ -2,8 +2,8 @@ namespace Seed\Errors; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UnauthorizedRequestErrorBody extends SerializableType { diff --git a/seed/php-model/basic-auth/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/basic-auth/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/EnumTest.php b/seed/php-model/basic-auth/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/basic-auth/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/basic-auth/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/TestTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/ArrayType.php b/seed/php-model/bearer-token-environment-variable/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/bearer-token-environment-variable/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Constant.php b/seed/php-model/bearer-token-environment-variable/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/bearer-token-environment-variable/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonDeserializer.php b/seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonEncoder.php b/seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Json/SerializableType.php b/seed/php-model/bearer-token-environment-variable/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Json/Utils.php b/seed/php-model/bearer-token-environment-variable/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/JsonDecoder.php b/seed/php-model/bearer-token-environment-variable/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/bearer-token-environment-variable/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/JsonDeserializer.php b/seed/php-model/bearer-token-environment-variable/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/bearer-token-environment-variable/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/JsonEncoder.php b/seed/php-model/bearer-token-environment-variable/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/bearer-token-environment-variable/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/SerializableType.php b/seed/php-model/bearer-token-environment-variable/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/bearer-token-environment-variable/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Types/ArrayType.php b/seed/php-model/bearer-token-environment-variable/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Types/Constant.php b/seed/php-model/bearer-token-environment-variable/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Union.php b/seed/php-model/bearer-token-environment-variable/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/bearer-token-environment-variable/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/src/Core/Utils.php b/seed/php-model/bearer-token-environment-variable/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/bearer-token-environment-variable/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/EnumTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/TestTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/bytes/src/Core/ArrayType.php b/seed/php-model/bytes/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/bytes/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/bytes/src/Core/Constant.php b/seed/php-model/bytes/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/bytes/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/bytes/src/Core/Json/JsonDeserializer.php b/seed/php-model/bytes/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/bytes/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/bytes/src/Core/Json/JsonEncoder.php b/seed/php-model/bytes/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/bytes/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/bytes/src/Core/Json/SerializableType.php b/seed/php-model/bytes/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/bytes/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/bytes/src/Core/Json/Utils.php b/seed/php-model/bytes/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/bytes/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/bytes/src/Core/JsonDecoder.php b/seed/php-model/bytes/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/bytes/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/bytes/src/Core/JsonDeserializer.php b/seed/php-model/bytes/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/bytes/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/bytes/src/Core/JsonEncoder.php b/seed/php-model/bytes/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/bytes/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/bytes/src/Core/SerializableType.php b/seed/php-model/bytes/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/bytes/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/bytes/src/Core/Types/ArrayType.php b/seed/php-model/bytes/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/bytes/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/bytes/src/Core/Types/Constant.php b/seed/php-model/bytes/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/bytes/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/bytes/src/Core/Union.php b/seed/php-model/bytes/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/bytes/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/bytes/src/Core/Utils.php b/seed/php-model/bytes/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/bytes/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/bytes/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/EnumTest.php b/seed/php-model/bytes/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/bytes/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/bytes/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/TestTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/bytes/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/bytes/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/bytes/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/circular-references-advanced/src/A/A.php b/seed/php-model/circular-references-advanced/src/A/A.php index 494f89a07bd..ad86a0bec70 100644 --- a/seed/php-model/circular-references-advanced/src/A/A.php +++ b/seed/php-model/circular-references-advanced/src/A/A.php @@ -2,7 +2,7 @@ namespace Seed\A; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class A extends SerializableType { diff --git a/seed/php-model/circular-references-advanced/src/Ast/ObjectFieldValue.php b/seed/php-model/circular-references-advanced/src/Ast/ObjectFieldValue.php index 1d38f2cf30d..a0bc60cd814 100644 --- a/seed/php-model/circular-references-advanced/src/Ast/ObjectFieldValue.php +++ b/seed/php-model/circular-references-advanced/src/Ast/ObjectFieldValue.php @@ -2,8 +2,8 @@ namespace Seed\Ast; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * This type allows us to test a circular reference with a union type (see FieldValue). diff --git a/seed/php-model/circular-references-advanced/src/Ast/ObjectValue.php b/seed/php-model/circular-references-advanced/src/Ast/ObjectValue.php index 0914e939886..2a211a8d6bb 100644 --- a/seed/php-model/circular-references-advanced/src/Ast/ObjectValue.php +++ b/seed/php-model/circular-references-advanced/src/Ast/ObjectValue.php @@ -2,7 +2,7 @@ namespace Seed\Ast; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ObjectValue extends SerializableType { diff --git a/seed/php-model/circular-references-advanced/src/Core/ArrayType.php b/seed/php-model/circular-references-advanced/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/circular-references-advanced/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/circular-references-advanced/src/Core/Constant.php b/seed/php-model/circular-references-advanced/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/circular-references-advanced/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/circular-references-advanced/src/Core/Json/JsonDeserializer.php b/seed/php-model/circular-references-advanced/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/circular-references-advanced/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/circular-references-advanced/src/Core/Json/JsonEncoder.php b/seed/php-model/circular-references-advanced/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/circular-references-advanced/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/circular-references-advanced/src/Core/Json/SerializableType.php b/seed/php-model/circular-references-advanced/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/circular-references-advanced/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/circular-references-advanced/src/Core/Json/Utils.php b/seed/php-model/circular-references-advanced/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/circular-references-advanced/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/circular-references-advanced/src/Core/JsonDecoder.php b/seed/php-model/circular-references-advanced/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/circular-references-advanced/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/circular-references-advanced/src/Core/JsonDeserializer.php b/seed/php-model/circular-references-advanced/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/circular-references-advanced/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/circular-references-advanced/src/Core/JsonEncoder.php b/seed/php-model/circular-references-advanced/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/circular-references-advanced/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/circular-references-advanced/src/Core/SerializableType.php b/seed/php-model/circular-references-advanced/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/circular-references-advanced/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/circular-references-advanced/src/Core/Types/ArrayType.php b/seed/php-model/circular-references-advanced/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/circular-references-advanced/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/circular-references-advanced/src/Core/Types/Constant.php b/seed/php-model/circular-references-advanced/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/circular-references-advanced/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/circular-references-advanced/src/Core/Union.php b/seed/php-model/circular-references-advanced/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/circular-references-advanced/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/circular-references-advanced/src/Core/Utils.php b/seed/php-model/circular-references-advanced/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/circular-references-advanced/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/circular-references-advanced/src/ImportingA.php b/seed/php-model/circular-references-advanced/src/ImportingA.php index cd116f3ee39..21daa9f817c 100644 --- a/seed/php-model/circular-references-advanced/src/ImportingA.php +++ b/seed/php-model/circular-references-advanced/src/ImportingA.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\A\A; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class ImportingA extends SerializableType { diff --git a/seed/php-model/circular-references-advanced/src/RootType.php b/seed/php-model/circular-references-advanced/src/RootType.php index 5516cf3cfdc..6e4ed49fdca 100644 --- a/seed/php-model/circular-references-advanced/src/RootType.php +++ b/seed/php-model/circular-references-advanced/src/RootType.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RootType extends SerializableType { diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/EnumTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/circular-references-advanced/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/TestTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/circular-references/src/A/A.php b/seed/php-model/circular-references/src/A/A.php index 494f89a07bd..ad86a0bec70 100644 --- a/seed/php-model/circular-references/src/A/A.php +++ b/seed/php-model/circular-references/src/A/A.php @@ -2,7 +2,7 @@ namespace Seed\A; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class A extends SerializableType { diff --git a/seed/php-model/circular-references/src/Ast/ObjectValue.php b/seed/php-model/circular-references/src/Ast/ObjectValue.php index 0914e939886..2a211a8d6bb 100644 --- a/seed/php-model/circular-references/src/Ast/ObjectValue.php +++ b/seed/php-model/circular-references/src/Ast/ObjectValue.php @@ -2,7 +2,7 @@ namespace Seed\Ast; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ObjectValue extends SerializableType { diff --git a/seed/php-model/circular-references/src/Core/ArrayType.php b/seed/php-model/circular-references/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/circular-references/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/circular-references/src/Core/Constant.php b/seed/php-model/circular-references/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/circular-references/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/circular-references/src/Core/Json/JsonDeserializer.php b/seed/php-model/circular-references/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/circular-references/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/circular-references/src/Core/Json/JsonEncoder.php b/seed/php-model/circular-references/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/circular-references/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/circular-references/src/Core/Json/SerializableType.php b/seed/php-model/circular-references/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/circular-references/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/circular-references/src/Core/Json/Utils.php b/seed/php-model/circular-references/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/circular-references/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/circular-references/src/Core/JsonDecoder.php b/seed/php-model/circular-references/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/circular-references/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/circular-references/src/Core/JsonDeserializer.php b/seed/php-model/circular-references/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/circular-references/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/circular-references/src/Core/JsonEncoder.php b/seed/php-model/circular-references/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/circular-references/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/circular-references/src/Core/SerializableType.php b/seed/php-model/circular-references/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/circular-references/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/circular-references/src/Core/Types/ArrayType.php b/seed/php-model/circular-references/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/circular-references/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/circular-references/src/Core/Types/Constant.php b/seed/php-model/circular-references/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/circular-references/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/circular-references/src/Core/Union.php b/seed/php-model/circular-references/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/circular-references/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/circular-references/src/Core/Utils.php b/seed/php-model/circular-references/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/circular-references/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/circular-references/src/ImportingA.php b/seed/php-model/circular-references/src/ImportingA.php index cd116f3ee39..21daa9f817c 100644 --- a/seed/php-model/circular-references/src/ImportingA.php +++ b/seed/php-model/circular-references/src/ImportingA.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\A\A; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class ImportingA extends SerializableType { diff --git a/seed/php-model/circular-references/src/RootType.php b/seed/php-model/circular-references/src/RootType.php index 5516cf3cfdc..6e4ed49fdca 100644 --- a/seed/php-model/circular-references/src/RootType.php +++ b/seed/php-model/circular-references/src/RootType.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RootType extends SerializableType { diff --git a/seed/php-model/circular-references/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/circular-references/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/EnumTest.php b/seed/php-model/circular-references/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/circular-references/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/circular-references/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/circular-references/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/circular-references/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/TestTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/cross-package-type-names/src/Core/ArrayType.php b/seed/php-model/cross-package-type-names/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/cross-package-type-names/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/cross-package-type-names/src/Core/Constant.php b/seed/php-model/cross-package-type-names/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/cross-package-type-names/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/cross-package-type-names/src/Core/Json/JsonDeserializer.php b/seed/php-model/cross-package-type-names/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/cross-package-type-names/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/cross-package-type-names/src/Core/Json/JsonEncoder.php b/seed/php-model/cross-package-type-names/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/cross-package-type-names/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/cross-package-type-names/src/Core/Json/SerializableType.php b/seed/php-model/cross-package-type-names/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/cross-package-type-names/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/cross-package-type-names/src/Core/Json/Utils.php b/seed/php-model/cross-package-type-names/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/cross-package-type-names/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/cross-package-type-names/src/Core/JsonDecoder.php b/seed/php-model/cross-package-type-names/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/cross-package-type-names/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/cross-package-type-names/src/Core/JsonDeserializer.php b/seed/php-model/cross-package-type-names/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/cross-package-type-names/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/cross-package-type-names/src/Core/JsonEncoder.php b/seed/php-model/cross-package-type-names/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/cross-package-type-names/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/cross-package-type-names/src/Core/SerializableType.php b/seed/php-model/cross-package-type-names/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/cross-package-type-names/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/cross-package-type-names/src/Core/Types/ArrayType.php b/seed/php-model/cross-package-type-names/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/cross-package-type-names/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/cross-package-type-names/src/Core/Types/Constant.php b/seed/php-model/cross-package-type-names/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/cross-package-type-names/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/cross-package-type-names/src/Core/Union.php b/seed/php-model/cross-package-type-names/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/cross-package-type-names/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/cross-package-type-names/src/Core/Utils.php b/seed/php-model/cross-package-type-names/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/cross-package-type-names/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/cross-package-type-names/src/FolderA/Service/Response.php b/seed/php-model/cross-package-type-names/src/FolderA/Service/Response.php index 627505a094a..1555b3f434c 100644 --- a/seed/php-model/cross-package-type-names/src/FolderA/Service/Response.php +++ b/seed/php-model/cross-package-type-names/src/FolderA/Service/Response.php @@ -2,9 +2,9 @@ namespace Seed\FolderA\Service; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderB\Common\Foo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-model/cross-package-type-names/src/FolderB/Common/Foo.php b/seed/php-model/cross-package-type-names/src/FolderB/Common/Foo.php index fca8033f216..95a011b28d7 100644 --- a/seed/php-model/cross-package-type-names/src/FolderB/Common/Foo.php +++ b/seed/php-model/cross-package-type-names/src/FolderB/Common/Foo.php @@ -2,9 +2,9 @@ namespace Seed\FolderB\Common; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderC\Common\Foo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Foo extends SerializableType { diff --git a/seed/php-model/cross-package-type-names/src/FolderC/Common/Foo.php b/seed/php-model/cross-package-type-names/src/FolderC/Common/Foo.php index c85c9dc6bb3..f1b07684342 100644 --- a/seed/php-model/cross-package-type-names/src/FolderC/Common/Foo.php +++ b/seed/php-model/cross-package-type-names/src/FolderC/Common/Foo.php @@ -2,8 +2,8 @@ namespace Seed\FolderC\Common; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Foo extends SerializableType { diff --git a/seed/php-model/cross-package-type-names/src/FolderD/Service/Response.php b/seed/php-model/cross-package-type-names/src/FolderD/Service/Response.php index 7d84b9d224f..e1cb59d1695 100644 --- a/seed/php-model/cross-package-type-names/src/FolderD/Service/Response.php +++ b/seed/php-model/cross-package-type-names/src/FolderD/Service/Response.php @@ -2,9 +2,9 @@ namespace Seed\FolderD\Service; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderB\Common\Foo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-model/cross-package-type-names/src/Foo/ImportingType.php b/seed/php-model/cross-package-type-names/src/Foo/ImportingType.php index 803575e6042..09a46b2aa74 100644 --- a/seed/php-model/cross-package-type-names/src/Foo/ImportingType.php +++ b/seed/php-model/cross-package-type-names/src/Foo/ImportingType.php @@ -2,8 +2,8 @@ namespace Seed\Foo; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ImportingType extends SerializableType { diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/EnumTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/TestTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/custom-auth/src/Core/ArrayType.php b/seed/php-model/custom-auth/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/custom-auth/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/custom-auth/src/Core/Constant.php b/seed/php-model/custom-auth/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/custom-auth/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/custom-auth/src/Core/Json/JsonDeserializer.php b/seed/php-model/custom-auth/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/custom-auth/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/custom-auth/src/Core/Json/JsonEncoder.php b/seed/php-model/custom-auth/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/custom-auth/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/custom-auth/src/Core/Json/SerializableType.php b/seed/php-model/custom-auth/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/custom-auth/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/custom-auth/src/Core/Json/Utils.php b/seed/php-model/custom-auth/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/custom-auth/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/custom-auth/src/Core/JsonDecoder.php b/seed/php-model/custom-auth/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/custom-auth/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/custom-auth/src/Core/JsonDeserializer.php b/seed/php-model/custom-auth/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/custom-auth/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/custom-auth/src/Core/JsonEncoder.php b/seed/php-model/custom-auth/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/custom-auth/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/custom-auth/src/Core/SerializableType.php b/seed/php-model/custom-auth/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/custom-auth/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/custom-auth/src/Core/Types/ArrayType.php b/seed/php-model/custom-auth/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/custom-auth/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/custom-auth/src/Core/Types/Constant.php b/seed/php-model/custom-auth/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/custom-auth/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/custom-auth/src/Core/Union.php b/seed/php-model/custom-auth/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/custom-auth/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/custom-auth/src/Core/Utils.php b/seed/php-model/custom-auth/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/custom-auth/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/custom-auth/src/Errors/UnauthorizedRequestErrorBody.php b/seed/php-model/custom-auth/src/Errors/UnauthorizedRequestErrorBody.php index 60fab695cf1..8f30396a292 100644 --- a/seed/php-model/custom-auth/src/Errors/UnauthorizedRequestErrorBody.php +++ b/seed/php-model/custom-auth/src/Errors/UnauthorizedRequestErrorBody.php @@ -2,8 +2,8 @@ namespace Seed\Errors; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UnauthorizedRequestErrorBody extends SerializableType { diff --git a/seed/php-model/custom-auth/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/custom-auth/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/EnumTest.php b/seed/php-model/custom-auth/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/custom-auth/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/custom-auth/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/TestTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/enum/src/Core/ArrayType.php b/seed/php-model/enum/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/enum/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/enum/src/Core/Constant.php b/seed/php-model/enum/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/enum/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/enum/src/Core/Json/JsonDeserializer.php b/seed/php-model/enum/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/enum/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/enum/src/Core/Json/JsonEncoder.php b/seed/php-model/enum/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/enum/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/enum/src/Core/Json/SerializableType.php b/seed/php-model/enum/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/enum/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/enum/src/Core/Json/Utils.php b/seed/php-model/enum/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/enum/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/enum/src/Core/JsonDecoder.php b/seed/php-model/enum/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/enum/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/enum/src/Core/JsonDeserializer.php b/seed/php-model/enum/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/enum/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/enum/src/Core/JsonEncoder.php b/seed/php-model/enum/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/enum/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/enum/src/Core/SerializableType.php b/seed/php-model/enum/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/enum/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/enum/src/Core/Types/ArrayType.php b/seed/php-model/enum/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/enum/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/enum/src/Core/Types/Constant.php b/seed/php-model/enum/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/enum/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/enum/src/Core/Union.php b/seed/php-model/enum/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/enum/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/enum/src/Core/Utils.php b/seed/php-model/enum/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/enum/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/enum/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/EnumTest.php b/seed/php-model/enum/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/enum/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/enum/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/enum/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/enum/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/enum/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/enum/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/enum/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/enum/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/enum/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/enum/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/TestTypeTest.php b/seed/php-model/enum/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/enum/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/enum/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/enum/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/enum/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/error-property/src/Core/ArrayType.php b/seed/php-model/error-property/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/error-property/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/error-property/src/Core/Constant.php b/seed/php-model/error-property/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/error-property/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/error-property/src/Core/Json/JsonDeserializer.php b/seed/php-model/error-property/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/error-property/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/error-property/src/Core/Json/JsonEncoder.php b/seed/php-model/error-property/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/error-property/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/error-property/src/Core/Json/SerializableType.php b/seed/php-model/error-property/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/error-property/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/error-property/src/Core/Json/Utils.php b/seed/php-model/error-property/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/error-property/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/error-property/src/Core/JsonDecoder.php b/seed/php-model/error-property/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/error-property/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/error-property/src/Core/JsonDeserializer.php b/seed/php-model/error-property/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/error-property/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/error-property/src/Core/JsonEncoder.php b/seed/php-model/error-property/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/error-property/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/error-property/src/Core/SerializableType.php b/seed/php-model/error-property/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/error-property/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/error-property/src/Core/Types/ArrayType.php b/seed/php-model/error-property/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/error-property/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/error-property/src/Core/Types/Constant.php b/seed/php-model/error-property/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/error-property/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/error-property/src/Core/Union.php b/seed/php-model/error-property/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/error-property/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/error-property/src/Core/Utils.php b/seed/php-model/error-property/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/error-property/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/error-property/src/Errors/PropertyBasedErrorTestBody.php b/seed/php-model/error-property/src/Errors/PropertyBasedErrorTestBody.php index 6556c55078e..2a98cf547ff 100644 --- a/seed/php-model/error-property/src/Errors/PropertyBasedErrorTestBody.php +++ b/seed/php-model/error-property/src/Errors/PropertyBasedErrorTestBody.php @@ -2,8 +2,8 @@ namespace Seed\Errors; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class PropertyBasedErrorTestBody extends SerializableType { diff --git a/seed/php-model/error-property/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/error-property/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/EnumTest.php b/seed/php-model/error-property/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/error-property/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/error-property/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/TestTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/error-property/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/error-property/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/error-property/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/examples/src/Commons/Types/Metadata.php b/seed/php-model/examples/src/Commons/Types/Metadata.php index c646434400a..19cc4a50341 100644 --- a/seed/php-model/examples/src/Commons/Types/Metadata.php +++ b/seed/php-model/examples/src/Commons/Types/Metadata.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Metadata extends SerializableType { diff --git a/seed/php-model/examples/src/Core/ArrayType.php b/seed/php-model/examples/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/examples/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/examples/src/Core/Constant.php b/seed/php-model/examples/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/examples/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/examples/src/Core/Json/JsonDeserializer.php b/seed/php-model/examples/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/examples/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/examples/src/Core/Json/JsonEncoder.php b/seed/php-model/examples/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/examples/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/examples/src/Core/Json/SerializableType.php b/seed/php-model/examples/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/examples/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/examples/src/Core/Json/Utils.php b/seed/php-model/examples/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/examples/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/examples/src/Core/JsonDecoder.php b/seed/php-model/examples/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/examples/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/examples/src/Core/JsonDeserializer.php b/seed/php-model/examples/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/examples/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/examples/src/Core/JsonEncoder.php b/seed/php-model/examples/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/examples/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/examples/src/Core/SerializableType.php b/seed/php-model/examples/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/examples/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/examples/src/Core/Types/ArrayType.php b/seed/php-model/examples/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/examples/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/examples/src/Core/Types/Constant.php b/seed/php-model/examples/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/examples/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/examples/src/Core/Union.php b/seed/php-model/examples/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/examples/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/examples/src/Core/Utils.php b/seed/php-model/examples/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/examples/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/examples/src/Identifier.php b/seed/php-model/examples/src/Identifier.php index 48363b1db26..504efbe1e3c 100644 --- a/seed/php-model/examples/src/Identifier.php +++ b/seed/php-model/examples/src/Identifier.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Identifier extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Actor.php b/seed/php-model/examples/src/Types/Actor.php index f8e7c7b6426..0faee48d81a 100644 --- a/seed/php-model/examples/src/Types/Actor.php +++ b/seed/php-model/examples/src/Types/Actor.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Actor extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Actress.php b/seed/php-model/examples/src/Types/Actress.php index faffd7dcc93..29e60cdcf21 100644 --- a/seed/php-model/examples/src/Types/Actress.php +++ b/seed/php-model/examples/src/Types/Actress.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Actress extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Directory.php b/seed/php-model/examples/src/Types/Directory.php index c81725a96a6..920acd4be7b 100644 --- a/seed/php-model/examples/src/Types/Directory.php +++ b/seed/php-model/examples/src/Types/Directory.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Directory extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Entity.php b/seed/php-model/examples/src/Types/Entity.php index c94f68a9794..da0c7a18f04 100644 --- a/seed/php-model/examples/src/Types/Entity.php +++ b/seed/php-model/examples/src/Types/Entity.php @@ -2,10 +2,10 @@ namespace Seed\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\BasicType; use Seed\ComplexType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Entity extends SerializableType { diff --git a/seed/php-model/examples/src/Types/ExceptionInfo.php b/seed/php-model/examples/src/Types/ExceptionInfo.php index 561e3cc4bdc..6ff225c4775 100644 --- a/seed/php-model/examples/src/Types/ExceptionInfo.php +++ b/seed/php-model/examples/src/Types/ExceptionInfo.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExceptionInfo extends SerializableType { diff --git a/seed/php-model/examples/src/Types/ExtendedMovie.php b/seed/php-model/examples/src/Types/ExtendedMovie.php index 7d2bcb1206a..4c313c65c61 100644 --- a/seed/php-model/examples/src/Types/ExtendedMovie.php +++ b/seed/php-model/examples/src/Types/ExtendedMovie.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ExtendedMovie extends SerializableType { diff --git a/seed/php-model/examples/src/Types/File.php b/seed/php-model/examples/src/Types/File.php index 7e5ace91289..0ea38d23535 100644 --- a/seed/php-model/examples/src/Types/File.php +++ b/seed/php-model/examples/src/Types/File.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class File extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Migration.php b/seed/php-model/examples/src/Types/Migration.php index c8d06bd197d..a0109517a99 100644 --- a/seed/php-model/examples/src/Types/Migration.php +++ b/seed/php-model/examples/src/Types/Migration.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Migration extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Moment.php b/seed/php-model/examples/src/Types/Moment.php index faeb8d68ac4..35b3113a090 100644 --- a/seed/php-model/examples/src/Types/Moment.php +++ b/seed/php-model/examples/src/Types/Moment.php @@ -2,10 +2,10 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use DateTime; -use Seed\Core\DateType; +use Seed\Core\Types\DateType; class Moment extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Movie.php b/seed/php-model/examples/src/Types/Movie.php index 71ceb88ae23..2e6eef7dc6c 100644 --- a/seed/php-model/examples/src/Types/Movie.php +++ b/seed/php-model/examples/src/Types/Movie.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Movie extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Node.php b/seed/php-model/examples/src/Types/Node.php index bcdac181253..a72364c431a 100644 --- a/seed/php-model/examples/src/Types/Node.php +++ b/seed/php-model/examples/src/Types/Node.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Node extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Request.php b/seed/php-model/examples/src/Types/Request.php index 1e1ff7028fd..77bcaebc8bc 100644 --- a/seed/php-model/examples/src/Types/Request.php +++ b/seed/php-model/examples/src/Types/Request.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Request extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Response.php b/seed/php-model/examples/src/Types/Response.php index ff83977fe56..932e4736fd7 100644 --- a/seed/php-model/examples/src/Types/Response.php +++ b/seed/php-model/examples/src/Types/Response.php @@ -2,10 +2,10 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Identifier; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class Response extends SerializableType { diff --git a/seed/php-model/examples/src/Types/ResponseType.php b/seed/php-model/examples/src/Types/ResponseType.php index 6bdca9a0b71..302901443bb 100644 --- a/seed/php-model/examples/src/Types/ResponseType.php +++ b/seed/php-model/examples/src/Types/ResponseType.php @@ -2,10 +2,10 @@ namespace Seed\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\BasicType; use Seed\ComplexType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class ResponseType extends SerializableType { diff --git a/seed/php-model/examples/src/Types/StuntDouble.php b/seed/php-model/examples/src/Types/StuntDouble.php index f207c0d4b01..eb63f0ad11f 100644 --- a/seed/php-model/examples/src/Types/StuntDouble.php +++ b/seed/php-model/examples/src/Types/StuntDouble.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StuntDouble extends SerializableType { diff --git a/seed/php-model/examples/src/Types/Tree.php b/seed/php-model/examples/src/Types/Tree.php index 5635dbaef47..4d1ad0ebb5e 100644 --- a/seed/php-model/examples/src/Types/Tree.php +++ b/seed/php-model/examples/src/Types/Tree.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Tree extends SerializableType { diff --git a/seed/php-model/examples/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/examples/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/EnumTest.php b/seed/php-model/examples/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/examples/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/examples/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/examples/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/examples/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/examples/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/examples/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/examples/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/examples/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/examples/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/examples/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/TestTypeTest.php b/seed/php-model/examples/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/examples/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/examples/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/examples/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/examples/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/exhaustive/src/Core/ArrayType.php b/seed/php-model/exhaustive/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/exhaustive/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/exhaustive/src/Core/Constant.php b/seed/php-model/exhaustive/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/exhaustive/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/exhaustive/src/Core/Json/JsonDeserializer.php b/seed/php-model/exhaustive/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/exhaustive/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/exhaustive/src/Core/Json/JsonEncoder.php b/seed/php-model/exhaustive/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/exhaustive/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/exhaustive/src/Core/Json/SerializableType.php b/seed/php-model/exhaustive/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/exhaustive/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/exhaustive/src/Core/Json/Utils.php b/seed/php-model/exhaustive/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/exhaustive/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/exhaustive/src/Core/JsonDecoder.php b/seed/php-model/exhaustive/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/exhaustive/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/exhaustive/src/Core/JsonDeserializer.php b/seed/php-model/exhaustive/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/exhaustive/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/exhaustive/src/Core/JsonEncoder.php b/seed/php-model/exhaustive/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/exhaustive/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/exhaustive/src/Core/SerializableType.php b/seed/php-model/exhaustive/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/exhaustive/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/exhaustive/src/Core/Types/ArrayType.php b/seed/php-model/exhaustive/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/exhaustive/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/exhaustive/src/Core/Types/Constant.php b/seed/php-model/exhaustive/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/exhaustive/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/exhaustive/src/Core/Union.php b/seed/php-model/exhaustive/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/exhaustive/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/exhaustive/src/Core/Utils.php b/seed/php-model/exhaustive/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/exhaustive/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/exhaustive/src/GeneralErrors/BadObjectRequestInfo.php b/seed/php-model/exhaustive/src/GeneralErrors/BadObjectRequestInfo.php index c9b6327cdf1..27066a22a9a 100644 --- a/seed/php-model/exhaustive/src/GeneralErrors/BadObjectRequestInfo.php +++ b/seed/php-model/exhaustive/src/GeneralErrors/BadObjectRequestInfo.php @@ -2,8 +2,8 @@ namespace Seed\GeneralErrors; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BadObjectRequestInfo extends SerializableType { diff --git a/seed/php-model/exhaustive/src/Types/Object/DoubleOptional.php b/seed/php-model/exhaustive/src/Types/Object/DoubleOptional.php index 4c81c370991..0e759df2b59 100644 --- a/seed/php-model/exhaustive/src/Types/Object/DoubleOptional.php +++ b/seed/php-model/exhaustive/src/Types/Object/DoubleOptional.php @@ -2,8 +2,8 @@ namespace Seed\Types\Object; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DoubleOptional extends SerializableType { diff --git a/seed/php-model/exhaustive/src/Types/Object/NestedObjectWithOptionalField.php b/seed/php-model/exhaustive/src/Types/Object/NestedObjectWithOptionalField.php index 3f0067a812c..5ab4e24153f 100644 --- a/seed/php-model/exhaustive/src/Types/Object/NestedObjectWithOptionalField.php +++ b/seed/php-model/exhaustive/src/Types/Object/NestedObjectWithOptionalField.php @@ -2,8 +2,8 @@ namespace Seed\Types\Object; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedObjectWithOptionalField extends SerializableType { diff --git a/seed/php-model/exhaustive/src/Types/Object/NestedObjectWithRequiredField.php b/seed/php-model/exhaustive/src/Types/Object/NestedObjectWithRequiredField.php index 2fcb2e70c42..135fc4b4292 100644 --- a/seed/php-model/exhaustive/src/Types/Object/NestedObjectWithRequiredField.php +++ b/seed/php-model/exhaustive/src/Types/Object/NestedObjectWithRequiredField.php @@ -2,8 +2,8 @@ namespace Seed\Types\Object; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedObjectWithRequiredField extends SerializableType { diff --git a/seed/php-model/exhaustive/src/Types/Object/ObjectWithMapOfMap.php b/seed/php-model/exhaustive/src/Types/Object/ObjectWithMapOfMap.php index b5c46b94712..796ed499790 100644 --- a/seed/php-model/exhaustive/src/Types/Object/ObjectWithMapOfMap.php +++ b/seed/php-model/exhaustive/src/Types/Object/ObjectWithMapOfMap.php @@ -2,9 +2,9 @@ namespace Seed\Types\Object; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ObjectWithMapOfMap extends SerializableType { diff --git a/seed/php-model/exhaustive/src/Types/Object/ObjectWithOptionalField.php b/seed/php-model/exhaustive/src/Types/Object/ObjectWithOptionalField.php index fe09483e7d0..1c313aef5f1 100644 --- a/seed/php-model/exhaustive/src/Types/Object/ObjectWithOptionalField.php +++ b/seed/php-model/exhaustive/src/Types/Object/ObjectWithOptionalField.php @@ -2,11 +2,11 @@ namespace Seed\Types\Object; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use DateTime; -use Seed\Core\DateType; -use Seed\Core\ArrayType; +use Seed\Core\Types\DateType; +use Seed\Core\Types\ArrayType; class ObjectWithOptionalField extends SerializableType { diff --git a/seed/php-model/exhaustive/src/Types/Object/ObjectWithRequiredField.php b/seed/php-model/exhaustive/src/Types/Object/ObjectWithRequiredField.php index d4b7d9587f7..6b1b7a08d44 100644 --- a/seed/php-model/exhaustive/src/Types/Object/ObjectWithRequiredField.php +++ b/seed/php-model/exhaustive/src/Types/Object/ObjectWithRequiredField.php @@ -2,8 +2,8 @@ namespace Seed\Types\Object; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ObjectWithRequiredField extends SerializableType { diff --git a/seed/php-model/exhaustive/src/Types/Union/Cat.php b/seed/php-model/exhaustive/src/Types/Union/Cat.php index fe2a52ae1ec..c4d041b1a59 100644 --- a/seed/php-model/exhaustive/src/Types/Union/Cat.php +++ b/seed/php-model/exhaustive/src/Types/Union/Cat.php @@ -2,8 +2,8 @@ namespace Seed\Types\Union; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Cat extends SerializableType { diff --git a/seed/php-model/exhaustive/src/Types/Union/Dog.php b/seed/php-model/exhaustive/src/Types/Union/Dog.php index e92179a9756..6e05cfa635a 100644 --- a/seed/php-model/exhaustive/src/Types/Union/Dog.php +++ b/seed/php-model/exhaustive/src/Types/Union/Dog.php @@ -2,8 +2,8 @@ namespace Seed\Types\Union; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Dog extends SerializableType { diff --git a/seed/php-model/exhaustive/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/exhaustive/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/EnumTest.php b/seed/php-model/exhaustive/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/exhaustive/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/exhaustive/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/TestTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/extends/src/Core/ArrayType.php b/seed/php-model/extends/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/extends/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/extends/src/Core/Constant.php b/seed/php-model/extends/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/extends/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/extends/src/Core/Json/JsonDeserializer.php b/seed/php-model/extends/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/extends/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/extends/src/Core/Json/JsonEncoder.php b/seed/php-model/extends/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/extends/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/extends/src/Core/Json/SerializableType.php b/seed/php-model/extends/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/extends/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/extends/src/Core/Json/Utils.php b/seed/php-model/extends/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/extends/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/extends/src/Core/JsonDecoder.php b/seed/php-model/extends/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/extends/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/extends/src/Core/JsonDeserializer.php b/seed/php-model/extends/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/extends/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/extends/src/Core/JsonEncoder.php b/seed/php-model/extends/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/extends/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/extends/src/Core/SerializableType.php b/seed/php-model/extends/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/extends/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/extends/src/Core/Types/ArrayType.php b/seed/php-model/extends/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/extends/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/extends/src/Core/Types/Constant.php b/seed/php-model/extends/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/extends/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/extends/src/Core/Union.php b/seed/php-model/extends/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/extends/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/extends/src/Core/Utils.php b/seed/php-model/extends/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/extends/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/extends/src/Docs.php b/seed/php-model/extends/src/Docs.php index 25819e5d332..c6b4204160d 100644 --- a/seed/php-model/extends/src/Docs.php +++ b/seed/php-model/extends/src/Docs.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Docs extends SerializableType { diff --git a/seed/php-model/extends/src/ExampleType.php b/seed/php-model/extends/src/ExampleType.php index ada1e73c6bf..7bc916aea74 100644 --- a/seed/php-model/extends/src/ExampleType.php +++ b/seed/php-model/extends/src/ExampleType.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExampleType extends SerializableType { diff --git a/seed/php-model/extends/src/Json.php b/seed/php-model/extends/src/Json.php index d3c68f18a03..9bc11b9c494 100644 --- a/seed/php-model/extends/src/Json.php +++ b/seed/php-model/extends/src/Json.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Json extends SerializableType { diff --git a/seed/php-model/extends/src/NestedType.php b/seed/php-model/extends/src/NestedType.php index c7976d3de52..2955b61bdc5 100644 --- a/seed/php-model/extends/src/NestedType.php +++ b/seed/php-model/extends/src/NestedType.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedType extends SerializableType { diff --git a/seed/php-model/extends/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/extends/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/EnumTest.php b/seed/php-model/extends/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/extends/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/extends/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/extends/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/extends/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/extends/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/extends/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/extends/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/extends/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/extends/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/extends/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/TestTypeTest.php b/seed/php-model/extends/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/extends/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/extends/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/extends/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/extends/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/extra-properties/src/Core/ArrayType.php b/seed/php-model/extra-properties/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/extra-properties/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/extra-properties/src/Core/Constant.php b/seed/php-model/extra-properties/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/extra-properties/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/extra-properties/src/Core/Json/JsonDeserializer.php b/seed/php-model/extra-properties/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/extra-properties/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/extra-properties/src/Core/Json/JsonEncoder.php b/seed/php-model/extra-properties/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/extra-properties/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/extra-properties/src/Core/Json/SerializableType.php b/seed/php-model/extra-properties/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/extra-properties/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/extra-properties/src/Core/Json/Utils.php b/seed/php-model/extra-properties/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/extra-properties/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/extra-properties/src/Core/JsonDecoder.php b/seed/php-model/extra-properties/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/extra-properties/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/extra-properties/src/Core/JsonDeserializer.php b/seed/php-model/extra-properties/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/extra-properties/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/extra-properties/src/Core/JsonEncoder.php b/seed/php-model/extra-properties/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/extra-properties/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/extra-properties/src/Core/SerializableType.php b/seed/php-model/extra-properties/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/extra-properties/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/extra-properties/src/Core/Types/ArrayType.php b/seed/php-model/extra-properties/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/extra-properties/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/extra-properties/src/Core/Types/Constant.php b/seed/php-model/extra-properties/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/extra-properties/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/extra-properties/src/Core/Union.php b/seed/php-model/extra-properties/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/extra-properties/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/extra-properties/src/Core/Utils.php b/seed/php-model/extra-properties/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/extra-properties/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/extra-properties/src/Failure.php b/seed/php-model/extra-properties/src/Failure.php index 1a527dad52b..58ee84ca694 100644 --- a/seed/php-model/extra-properties/src/Failure.php +++ b/seed/php-model/extra-properties/src/Failure.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Failure extends SerializableType { diff --git a/seed/php-model/extra-properties/src/User/User.php b/seed/php-model/extra-properties/src/User/User.php index a1be24384b7..ffc548aa8d3 100644 --- a/seed/php-model/extra-properties/src/User/User.php +++ b/seed/php-model/extra-properties/src/User/User.php @@ -2,8 +2,8 @@ namespace Seed\User; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-model/extra-properties/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/extra-properties/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/EnumTest.php b/seed/php-model/extra-properties/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/extra-properties/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/extra-properties/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/TestTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/file-download/src/Core/ArrayType.php b/seed/php-model/file-download/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/file-download/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/file-download/src/Core/Constant.php b/seed/php-model/file-download/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/file-download/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/file-download/src/Core/Json/JsonDeserializer.php b/seed/php-model/file-download/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/file-download/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/file-download/src/Core/Json/JsonEncoder.php b/seed/php-model/file-download/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/file-download/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/file-download/src/Core/Json/SerializableType.php b/seed/php-model/file-download/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/file-download/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/file-download/src/Core/Json/Utils.php b/seed/php-model/file-download/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/file-download/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/file-download/src/Core/JsonDecoder.php b/seed/php-model/file-download/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/file-download/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/file-download/src/Core/JsonDeserializer.php b/seed/php-model/file-download/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/file-download/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/file-download/src/Core/JsonEncoder.php b/seed/php-model/file-download/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/file-download/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/file-download/src/Core/SerializableType.php b/seed/php-model/file-download/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/file-download/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/file-download/src/Core/Types/ArrayType.php b/seed/php-model/file-download/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/file-download/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/file-download/src/Core/Types/Constant.php b/seed/php-model/file-download/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/file-download/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/file-download/src/Core/Union.php b/seed/php-model/file-download/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/file-download/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/file-download/src/Core/Utils.php b/seed/php-model/file-download/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/file-download/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/file-download/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/EnumTest.php b/seed/php-model/file-download/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/file-download/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/file-download/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/TestTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/file-download/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/file-download/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/file-download/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/file-upload/src/Core/ArrayType.php b/seed/php-model/file-upload/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/file-upload/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/file-upload/src/Core/Constant.php b/seed/php-model/file-upload/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/file-upload/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/file-upload/src/Core/Json/JsonDeserializer.php b/seed/php-model/file-upload/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/file-upload/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/file-upload/src/Core/Json/JsonEncoder.php b/seed/php-model/file-upload/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/file-upload/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/file-upload/src/Core/Json/SerializableType.php b/seed/php-model/file-upload/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/file-upload/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/file-upload/src/Core/Json/Utils.php b/seed/php-model/file-upload/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/file-upload/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/file-upload/src/Core/JsonDecoder.php b/seed/php-model/file-upload/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/file-upload/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/file-upload/src/Core/JsonDeserializer.php b/seed/php-model/file-upload/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/file-upload/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/file-upload/src/Core/JsonEncoder.php b/seed/php-model/file-upload/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/file-upload/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/file-upload/src/Core/SerializableType.php b/seed/php-model/file-upload/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/file-upload/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/file-upload/src/Core/Types/ArrayType.php b/seed/php-model/file-upload/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/file-upload/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/file-upload/src/Core/Types/Constant.php b/seed/php-model/file-upload/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/file-upload/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/file-upload/src/Core/Union.php b/seed/php-model/file-upload/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/file-upload/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/file-upload/src/Core/Utils.php b/seed/php-model/file-upload/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/file-upload/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/file-upload/src/Service/MyObject.php b/seed/php-model/file-upload/src/Service/MyObject.php index 0b190d773e9..df6219565fb 100644 --- a/seed/php-model/file-upload/src/Service/MyObject.php +++ b/seed/php-model/file-upload/src/Service/MyObject.php @@ -2,8 +2,8 @@ namespace Seed\Service; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class MyObject extends SerializableType { diff --git a/seed/php-model/file-upload/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/file-upload/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/EnumTest.php b/seed/php-model/file-upload/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/file-upload/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/file-upload/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/TestTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/folders/src/Core/ArrayType.php b/seed/php-model/folders/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/folders/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/folders/src/Core/Constant.php b/seed/php-model/folders/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/folders/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/folders/src/Core/Json/JsonDeserializer.php b/seed/php-model/folders/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/folders/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/folders/src/Core/Json/JsonEncoder.php b/seed/php-model/folders/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/folders/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/folders/src/Core/Json/SerializableType.php b/seed/php-model/folders/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/folders/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/folders/src/Core/Json/Utils.php b/seed/php-model/folders/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/folders/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/folders/src/Core/JsonDecoder.php b/seed/php-model/folders/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/folders/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/folders/src/Core/JsonDeserializer.php b/seed/php-model/folders/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/folders/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/folders/src/Core/JsonEncoder.php b/seed/php-model/folders/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/folders/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/folders/src/Core/SerializableType.php b/seed/php-model/folders/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/folders/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/folders/src/Core/Types/ArrayType.php b/seed/php-model/folders/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/folders/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/folders/src/Core/Types/Constant.php b/seed/php-model/folders/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/folders/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/folders/src/Core/Union.php b/seed/php-model/folders/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/folders/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/folders/src/Core/Utils.php b/seed/php-model/folders/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/folders/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/folders/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/EnumTest.php b/seed/php-model/folders/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/folders/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/folders/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/folders/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/folders/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/folders/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/folders/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/folders/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/folders/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/folders/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/folders/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/TestTypeTest.php b/seed/php-model/folders/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/folders/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/folders/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/folders/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/folders/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Column.php b/seed/php-model/grpc-proto-exhaustive/src/Column.php index c0cc5b4386c..80c1b1a581f 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Column.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Column.php @@ -2,10 +2,10 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class Column extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/ArrayType.php b/seed/php-model/grpc-proto-exhaustive/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Constant.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonDeserializer.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonEncoder.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Json/SerializableType.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Json/Utils.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDecoder.php b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDeserializer.php b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonEncoder.php b/seed/php-model/grpc-proto-exhaustive/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/SerializableType.php b/seed/php-model/grpc-proto-exhaustive/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Types/ArrayType.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Types/Constant.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Union.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/src/Core/Utils.php b/seed/php-model/grpc-proto-exhaustive/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/src/DeleteResponse.php b/seed/php-model/grpc-proto-exhaustive/src/DeleteResponse.php index 27e492bec09..fdeafd08017 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/DeleteResponse.php +++ b/seed/php-model/grpc-proto-exhaustive/src/DeleteResponse.php @@ -2,7 +2,7 @@ namespace Seed; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class DeleteResponse extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/DescribeResponse.php b/seed/php-model/grpc-proto-exhaustive/src/DescribeResponse.php index 000c313e9e7..f33a154f52e 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/DescribeResponse.php +++ b/seed/php-model/grpc-proto-exhaustive/src/DescribeResponse.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DescribeResponse extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/FetchResponse.php b/seed/php-model/grpc-proto-exhaustive/src/FetchResponse.php index 367b051fdcd..34ac4a57a10 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/FetchResponse.php +++ b/seed/php-model/grpc-proto-exhaustive/src/FetchResponse.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class FetchResponse extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/IndexedData.php b/seed/php-model/grpc-proto-exhaustive/src/IndexedData.php index 39478adb595..c1a74c4f3bd 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/IndexedData.php +++ b/seed/php-model/grpc-proto-exhaustive/src/IndexedData.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class IndexedData extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/ListElement.php b/seed/php-model/grpc-proto-exhaustive/src/ListElement.php index 40330ae3b0e..11dca4ec21e 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/ListElement.php +++ b/seed/php-model/grpc-proto-exhaustive/src/ListElement.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ListElement extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/ListResponse.php b/seed/php-model/grpc-proto-exhaustive/src/ListResponse.php index 3db98fa95ec..4f4009c1d57 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/ListResponse.php +++ b/seed/php-model/grpc-proto-exhaustive/src/ListResponse.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ListResponse extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/NamespaceSummary.php b/seed/php-model/grpc-proto-exhaustive/src/NamespaceSummary.php index 361fae7c53c..f2f375cc85a 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/NamespaceSummary.php +++ b/seed/php-model/grpc-proto-exhaustive/src/NamespaceSummary.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NamespaceSummary extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/Pagination.php b/seed/php-model/grpc-proto-exhaustive/src/Pagination.php index f2842226dbb..06a7bc92253 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Pagination.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Pagination.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Pagination extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php b/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php index c30b57e5a54..5cc30785f04 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php +++ b/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php @@ -2,10 +2,10 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class QueryColumn extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/QueryResponse.php b/seed/php-model/grpc-proto-exhaustive/src/QueryResponse.php index eb19f9f0cb1..5768bd3b9f9 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/QueryResponse.php +++ b/seed/php-model/grpc-proto-exhaustive/src/QueryResponse.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class QueryResponse extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/QueryResult.php b/seed/php-model/grpc-proto-exhaustive/src/QueryResult.php index ade796c1865..6f537b97487 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/QueryResult.php +++ b/seed/php-model/grpc-proto-exhaustive/src/QueryResult.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class QueryResult extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php b/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php index 22ce1d2861d..f0dab1f3235 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php +++ b/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php @@ -2,10 +2,10 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class ScoredColumn extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/UpdateResponse.php b/seed/php-model/grpc-proto-exhaustive/src/UpdateResponse.php index 293f79003b1..27af46e2c5f 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/UpdateResponse.php +++ b/seed/php-model/grpc-proto-exhaustive/src/UpdateResponse.php @@ -2,7 +2,7 @@ namespace Seed; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class UpdateResponse extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/UploadResponse.php b/seed/php-model/grpc-proto-exhaustive/src/UploadResponse.php index 1ed77a4aed4..dd20d60c045 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/UploadResponse.php +++ b/seed/php-model/grpc-proto-exhaustive/src/UploadResponse.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UploadResponse extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/src/Usage.php b/seed/php-model/grpc-proto-exhaustive/src/Usage.php index 4ebac5dc677..b6ccfd43642 100644 --- a/seed/php-model/grpc-proto-exhaustive/src/Usage.php +++ b/seed/php-model/grpc-proto-exhaustive/src/Usage.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Usage extends SerializableType { diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/EnumTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/TestTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/grpc-proto/src/Core/ArrayType.php b/seed/php-model/grpc-proto/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/grpc-proto/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/grpc-proto/src/Core/Constant.php b/seed/php-model/grpc-proto/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/grpc-proto/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/grpc-proto/src/Core/Json/JsonDeserializer.php b/seed/php-model/grpc-proto/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/grpc-proto/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/grpc-proto/src/Core/Json/JsonEncoder.php b/seed/php-model/grpc-proto/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/grpc-proto/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/grpc-proto/src/Core/Json/SerializableType.php b/seed/php-model/grpc-proto/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/grpc-proto/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/grpc-proto/src/Core/Json/Utils.php b/seed/php-model/grpc-proto/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/grpc-proto/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/grpc-proto/src/Core/JsonDecoder.php b/seed/php-model/grpc-proto/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/grpc-proto/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/grpc-proto/src/Core/JsonDeserializer.php b/seed/php-model/grpc-proto/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/grpc-proto/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/grpc-proto/src/Core/JsonEncoder.php b/seed/php-model/grpc-proto/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/grpc-proto/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/grpc-proto/src/Core/SerializableType.php b/seed/php-model/grpc-proto/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/grpc-proto/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/grpc-proto/src/Core/Types/ArrayType.php b/seed/php-model/grpc-proto/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/grpc-proto/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/grpc-proto/src/Core/Types/Constant.php b/seed/php-model/grpc-proto/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/grpc-proto/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/grpc-proto/src/Core/Union.php b/seed/php-model/grpc-proto/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/grpc-proto/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/grpc-proto/src/Core/Utils.php b/seed/php-model/grpc-proto/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/grpc-proto/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/grpc-proto/src/CreateResponse.php b/seed/php-model/grpc-proto/src/CreateResponse.php index e34a1a25b73..1bfbbb3951e 100644 --- a/seed/php-model/grpc-proto/src/CreateResponse.php +++ b/seed/php-model/grpc-proto/src/CreateResponse.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CreateResponse extends SerializableType { diff --git a/seed/php-model/grpc-proto/src/UserModel.php b/seed/php-model/grpc-proto/src/UserModel.php index 54516ab941a..ffa2046feef 100644 --- a/seed/php-model/grpc-proto/src/UserModel.php +++ b/seed/php-model/grpc-proto/src/UserModel.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\Union; class UserModel extends SerializableType { diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/EnumTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/TestTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/idempotency-headers/src/Core/ArrayType.php b/seed/php-model/idempotency-headers/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/idempotency-headers/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/idempotency-headers/src/Core/Constant.php b/seed/php-model/idempotency-headers/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/idempotency-headers/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/idempotency-headers/src/Core/Json/JsonDeserializer.php b/seed/php-model/idempotency-headers/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/idempotency-headers/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/idempotency-headers/src/Core/Json/JsonEncoder.php b/seed/php-model/idempotency-headers/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/idempotency-headers/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/idempotency-headers/src/Core/Json/SerializableType.php b/seed/php-model/idempotency-headers/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/idempotency-headers/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/idempotency-headers/src/Core/Json/Utils.php b/seed/php-model/idempotency-headers/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/idempotency-headers/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/idempotency-headers/src/Core/JsonDecoder.php b/seed/php-model/idempotency-headers/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/idempotency-headers/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/idempotency-headers/src/Core/JsonDeserializer.php b/seed/php-model/idempotency-headers/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/idempotency-headers/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/idempotency-headers/src/Core/JsonEncoder.php b/seed/php-model/idempotency-headers/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/idempotency-headers/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/idempotency-headers/src/Core/SerializableType.php b/seed/php-model/idempotency-headers/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/idempotency-headers/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/idempotency-headers/src/Core/Types/ArrayType.php b/seed/php-model/idempotency-headers/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/idempotency-headers/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/idempotency-headers/src/Core/Types/Constant.php b/seed/php-model/idempotency-headers/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/idempotency-headers/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/idempotency-headers/src/Core/Union.php b/seed/php-model/idempotency-headers/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/idempotency-headers/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/idempotency-headers/src/Core/Utils.php b/seed/php-model/idempotency-headers/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/idempotency-headers/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/EnumTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/TestTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/imdb/src/Core/ArrayType.php b/seed/php-model/imdb/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/imdb/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/imdb/src/Core/Constant.php b/seed/php-model/imdb/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/imdb/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/imdb/src/Core/Json/JsonDeserializer.php b/seed/php-model/imdb/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/imdb/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/imdb/src/Core/Json/JsonEncoder.php b/seed/php-model/imdb/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/imdb/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/imdb/src/Core/Json/SerializableType.php b/seed/php-model/imdb/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/imdb/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/imdb/src/Core/Json/Utils.php b/seed/php-model/imdb/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/imdb/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/imdb/src/Core/JsonDecoder.php b/seed/php-model/imdb/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/imdb/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/imdb/src/Core/JsonDeserializer.php b/seed/php-model/imdb/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/imdb/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/imdb/src/Core/JsonEncoder.php b/seed/php-model/imdb/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/imdb/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/imdb/src/Core/SerializableType.php b/seed/php-model/imdb/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/imdb/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/imdb/src/Core/Types/ArrayType.php b/seed/php-model/imdb/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/imdb/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/imdb/src/Core/Types/Constant.php b/seed/php-model/imdb/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/imdb/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/imdb/src/Core/Union.php b/seed/php-model/imdb/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/imdb/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/imdb/src/Core/Utils.php b/seed/php-model/imdb/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/imdb/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/imdb/src/Imdb/CreateMovieRequest.php b/seed/php-model/imdb/src/Imdb/CreateMovieRequest.php index 45949403f1f..75baa9b7d1d 100644 --- a/seed/php-model/imdb/src/Imdb/CreateMovieRequest.php +++ b/seed/php-model/imdb/src/Imdb/CreateMovieRequest.php @@ -2,8 +2,8 @@ namespace Seed\Imdb; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CreateMovieRequest extends SerializableType { diff --git a/seed/php-model/imdb/src/Imdb/Movie.php b/seed/php-model/imdb/src/Imdb/Movie.php index f38a52fcfee..86ef242e6ad 100644 --- a/seed/php-model/imdb/src/Imdb/Movie.php +++ b/seed/php-model/imdb/src/Imdb/Movie.php @@ -2,8 +2,8 @@ namespace Seed\Imdb; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Movie extends SerializableType { diff --git a/seed/php-model/imdb/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/imdb/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/EnumTest.php b/seed/php-model/imdb/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/imdb/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/imdb/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/TestTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/imdb/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/imdb/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/imdb/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/literal/src/Core/ArrayType.php b/seed/php-model/literal/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/literal/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/literal/src/Core/Constant.php b/seed/php-model/literal/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/literal/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/literal/src/Core/Json/JsonDeserializer.php b/seed/php-model/literal/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/literal/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/literal/src/Core/Json/JsonEncoder.php b/seed/php-model/literal/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/literal/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/literal/src/Core/Json/SerializableType.php b/seed/php-model/literal/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/literal/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/literal/src/Core/Json/Utils.php b/seed/php-model/literal/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/literal/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/literal/src/Core/JsonDecoder.php b/seed/php-model/literal/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/literal/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/literal/src/Core/JsonDeserializer.php b/seed/php-model/literal/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/literal/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/literal/src/Core/JsonEncoder.php b/seed/php-model/literal/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/literal/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/literal/src/Core/SerializableType.php b/seed/php-model/literal/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/literal/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/literal/src/Core/Types/ArrayType.php b/seed/php-model/literal/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/literal/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/literal/src/Core/Types/Constant.php b/seed/php-model/literal/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/literal/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/literal/src/Core/Union.php b/seed/php-model/literal/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/literal/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/literal/src/Core/Utils.php b/seed/php-model/literal/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/literal/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/literal/src/Inlined/ANestedLiteral.php b/seed/php-model/literal/src/Inlined/ANestedLiteral.php index 244ef3cf49f..4b76af6c02c 100644 --- a/seed/php-model/literal/src/Inlined/ANestedLiteral.php +++ b/seed/php-model/literal/src/Inlined/ANestedLiteral.php @@ -2,8 +2,8 @@ namespace Seed\Inlined; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ANestedLiteral extends SerializableType { diff --git a/seed/php-model/literal/src/Inlined/ATopLevelLiteral.php b/seed/php-model/literal/src/Inlined/ATopLevelLiteral.php index 7a187cdb176..72dcb3f1a10 100644 --- a/seed/php-model/literal/src/Inlined/ATopLevelLiteral.php +++ b/seed/php-model/literal/src/Inlined/ATopLevelLiteral.php @@ -2,8 +2,8 @@ namespace Seed\Inlined; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ATopLevelLiteral extends SerializableType { diff --git a/seed/php-model/literal/src/Reference/ContainerObject.php b/seed/php-model/literal/src/Reference/ContainerObject.php index adab545b643..4640478a356 100644 --- a/seed/php-model/literal/src/Reference/ContainerObject.php +++ b/seed/php-model/literal/src/Reference/ContainerObject.php @@ -2,9 +2,9 @@ namespace Seed\Reference; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ContainerObject extends SerializableType { diff --git a/seed/php-model/literal/src/Reference/NestedObjectWithLiterals.php b/seed/php-model/literal/src/Reference/NestedObjectWithLiterals.php index 8d0a3ad8178..182d043607f 100644 --- a/seed/php-model/literal/src/Reference/NestedObjectWithLiterals.php +++ b/seed/php-model/literal/src/Reference/NestedObjectWithLiterals.php @@ -2,8 +2,8 @@ namespace Seed\Reference; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedObjectWithLiterals extends SerializableType { diff --git a/seed/php-model/literal/src/Reference/SendRequest.php b/seed/php-model/literal/src/Reference/SendRequest.php index c466c33d98b..0c31b442b58 100644 --- a/seed/php-model/literal/src/Reference/SendRequest.php +++ b/seed/php-model/literal/src/Reference/SendRequest.php @@ -2,8 +2,8 @@ namespace Seed\Reference; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SendRequest extends SerializableType { diff --git a/seed/php-model/literal/src/SendResponse.php b/seed/php-model/literal/src/SendResponse.php index 1590416a628..02a0b80034b 100644 --- a/seed/php-model/literal/src/SendResponse.php +++ b/seed/php-model/literal/src/SendResponse.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SendResponse extends SerializableType { diff --git a/seed/php-model/literal/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/literal/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/EnumTest.php b/seed/php-model/literal/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/literal/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/literal/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/literal/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/literal/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/literal/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/literal/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/literal/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/literal/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/literal/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/literal/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/TestTypeTest.php b/seed/php-model/literal/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/literal/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/literal/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/literal/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/literal/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/mixed-case/src/Core/ArrayType.php b/seed/php-model/mixed-case/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/mixed-case/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/mixed-case/src/Core/Constant.php b/seed/php-model/mixed-case/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/mixed-case/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/mixed-case/src/Core/Json/JsonDeserializer.php b/seed/php-model/mixed-case/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/mixed-case/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/mixed-case/src/Core/Json/JsonEncoder.php b/seed/php-model/mixed-case/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/mixed-case/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/mixed-case/src/Core/Json/SerializableType.php b/seed/php-model/mixed-case/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/mixed-case/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/mixed-case/src/Core/Json/Utils.php b/seed/php-model/mixed-case/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/mixed-case/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/mixed-case/src/Core/JsonDecoder.php b/seed/php-model/mixed-case/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/mixed-case/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/mixed-case/src/Core/JsonDeserializer.php b/seed/php-model/mixed-case/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/mixed-case/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/mixed-case/src/Core/JsonEncoder.php b/seed/php-model/mixed-case/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/mixed-case/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/mixed-case/src/Core/SerializableType.php b/seed/php-model/mixed-case/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/mixed-case/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/mixed-case/src/Core/Types/ArrayType.php b/seed/php-model/mixed-case/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/mixed-case/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/mixed-case/src/Core/Types/Constant.php b/seed/php-model/mixed-case/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/mixed-case/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/mixed-case/src/Core/Union.php b/seed/php-model/mixed-case/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/mixed-case/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/mixed-case/src/Core/Utils.php b/seed/php-model/mixed-case/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/mixed-case/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/mixed-case/src/Service/NestedUser.php b/seed/php-model/mixed-case/src/Service/NestedUser.php index f7024f0d818..6d01b0724fe 100644 --- a/seed/php-model/mixed-case/src/Service/NestedUser.php +++ b/seed/php-model/mixed-case/src/Service/NestedUser.php @@ -2,8 +2,8 @@ namespace Seed\Service; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedUser extends SerializableType { diff --git a/seed/php-model/mixed-case/src/Service/Organization.php b/seed/php-model/mixed-case/src/Service/Organization.php index aa378d15408..5a4945468d9 100644 --- a/seed/php-model/mixed-case/src/Service/Organization.php +++ b/seed/php-model/mixed-case/src/Service/Organization.php @@ -2,8 +2,8 @@ namespace Seed\Service; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Organization extends SerializableType { diff --git a/seed/php-model/mixed-case/src/Service/User.php b/seed/php-model/mixed-case/src/Service/User.php index e186312f564..f0cdaeaad3a 100644 --- a/seed/php-model/mixed-case/src/Service/User.php +++ b/seed/php-model/mixed-case/src/Service/User.php @@ -2,9 +2,9 @@ namespace Seed\Service; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class User extends SerializableType { diff --git a/seed/php-model/mixed-case/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/mixed-case/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/EnumTest.php b/seed/php-model/mixed-case/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/mixed-case/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/mixed-case/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/TestTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/mixed-file-directory/src/Core/ArrayType.php b/seed/php-model/mixed-file-directory/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/mixed-file-directory/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/mixed-file-directory/src/Core/Constant.php b/seed/php-model/mixed-file-directory/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/mixed-file-directory/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/mixed-file-directory/src/Core/Json/JsonDeserializer.php b/seed/php-model/mixed-file-directory/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/mixed-file-directory/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/mixed-file-directory/src/Core/Json/JsonEncoder.php b/seed/php-model/mixed-file-directory/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/mixed-file-directory/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/mixed-file-directory/src/Core/Json/SerializableType.php b/seed/php-model/mixed-file-directory/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/mixed-file-directory/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/mixed-file-directory/src/Core/Json/Utils.php b/seed/php-model/mixed-file-directory/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/mixed-file-directory/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/mixed-file-directory/src/Core/JsonDecoder.php b/seed/php-model/mixed-file-directory/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/mixed-file-directory/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/mixed-file-directory/src/Core/JsonDeserializer.php b/seed/php-model/mixed-file-directory/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/mixed-file-directory/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/mixed-file-directory/src/Core/JsonEncoder.php b/seed/php-model/mixed-file-directory/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/mixed-file-directory/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/mixed-file-directory/src/Core/SerializableType.php b/seed/php-model/mixed-file-directory/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/mixed-file-directory/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/mixed-file-directory/src/Core/Types/ArrayType.php b/seed/php-model/mixed-file-directory/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/mixed-file-directory/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/mixed-file-directory/src/Core/Types/Constant.php b/seed/php-model/mixed-file-directory/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/mixed-file-directory/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/mixed-file-directory/src/Core/Union.php b/seed/php-model/mixed-file-directory/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/mixed-file-directory/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/mixed-file-directory/src/Core/Utils.php b/seed/php-model/mixed-file-directory/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/mixed-file-directory/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/mixed-file-directory/src/Organization/CreateOrganizationRequest.php b/seed/php-model/mixed-file-directory/src/Organization/CreateOrganizationRequest.php index 7782cade485..83e371138a2 100644 --- a/seed/php-model/mixed-file-directory/src/Organization/CreateOrganizationRequest.php +++ b/seed/php-model/mixed-file-directory/src/Organization/CreateOrganizationRequest.php @@ -2,8 +2,8 @@ namespace Seed\Organization; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CreateOrganizationRequest extends SerializableType { diff --git a/seed/php-model/mixed-file-directory/src/Organization/Organization.php b/seed/php-model/mixed-file-directory/src/Organization/Organization.php index 57bcd4498fb..7f2a75f658e 100644 --- a/seed/php-model/mixed-file-directory/src/Organization/Organization.php +++ b/seed/php-model/mixed-file-directory/src/Organization/Organization.php @@ -2,10 +2,10 @@ namespace Seed\Organization; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\User\User; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class Organization extends SerializableType { diff --git a/seed/php-model/mixed-file-directory/src/User/Events/Event.php b/seed/php-model/mixed-file-directory/src/User/Events/Event.php index 9d3715964e3..876ade063e6 100644 --- a/seed/php-model/mixed-file-directory/src/User/Events/Event.php +++ b/seed/php-model/mixed-file-directory/src/User/Events/Event.php @@ -2,8 +2,8 @@ namespace Seed\User\Events; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Event extends SerializableType { diff --git a/seed/php-model/mixed-file-directory/src/User/Events/Metadata/Metadata.php b/seed/php-model/mixed-file-directory/src/User/Events/Metadata/Metadata.php index 1540db16f42..1fc37237964 100644 --- a/seed/php-model/mixed-file-directory/src/User/Events/Metadata/Metadata.php +++ b/seed/php-model/mixed-file-directory/src/User/Events/Metadata/Metadata.php @@ -2,8 +2,8 @@ namespace Seed\User\Events\Metadata; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Metadata extends SerializableType { diff --git a/seed/php-model/mixed-file-directory/src/User/User.php b/seed/php-model/mixed-file-directory/src/User/User.php index e65a2efbb8e..4ce5e1a8eee 100644 --- a/seed/php-model/mixed-file-directory/src/User/User.php +++ b/seed/php-model/mixed-file-directory/src/User/User.php @@ -2,8 +2,8 @@ namespace Seed\User; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/EnumTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/TestTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/multi-line-docs/src/Core/ArrayType.php b/seed/php-model/multi-line-docs/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/multi-line-docs/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/multi-line-docs/src/Core/Constant.php b/seed/php-model/multi-line-docs/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/multi-line-docs/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/multi-line-docs/src/Core/Json/JsonDeserializer.php b/seed/php-model/multi-line-docs/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/multi-line-docs/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/multi-line-docs/src/Core/Json/JsonEncoder.php b/seed/php-model/multi-line-docs/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/multi-line-docs/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/multi-line-docs/src/Core/Json/SerializableType.php b/seed/php-model/multi-line-docs/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/multi-line-docs/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/multi-line-docs/src/Core/Json/Utils.php b/seed/php-model/multi-line-docs/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/multi-line-docs/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/multi-line-docs/src/Core/JsonDecoder.php b/seed/php-model/multi-line-docs/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/multi-line-docs/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/multi-line-docs/src/Core/JsonDeserializer.php b/seed/php-model/multi-line-docs/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/multi-line-docs/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/multi-line-docs/src/Core/JsonEncoder.php b/seed/php-model/multi-line-docs/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/multi-line-docs/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/multi-line-docs/src/Core/SerializableType.php b/seed/php-model/multi-line-docs/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/multi-line-docs/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/multi-line-docs/src/Core/Types/ArrayType.php b/seed/php-model/multi-line-docs/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/multi-line-docs/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/multi-line-docs/src/Core/Types/Constant.php b/seed/php-model/multi-line-docs/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/multi-line-docs/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/multi-line-docs/src/Core/Union.php b/seed/php-model/multi-line-docs/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/multi-line-docs/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/multi-line-docs/src/Core/Utils.php b/seed/php-model/multi-line-docs/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/multi-line-docs/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/multi-line-docs/src/User/User.php b/seed/php-model/multi-line-docs/src/User/User.php index ea64525c8f8..dddcb9abb47 100644 --- a/seed/php-model/multi-line-docs/src/User/User.php +++ b/seed/php-model/multi-line-docs/src/User/User.php @@ -2,8 +2,8 @@ namespace Seed\User; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * A user object. This type is used throughout the following APIs: diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/EnumTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/TestTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/ArrayType.php b/seed/php-model/multi-url-environment-no-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/multi-url-environment-no-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Constant.php b/seed/php-model/multi-url-environment-no-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/multi-url-environment-no-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonDeserializer.php b/seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonEncoder.php b/seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Json/SerializableType.php b/seed/php-model/multi-url-environment-no-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Json/Utils.php b/seed/php-model/multi-url-environment-no-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/JsonDecoder.php b/seed/php-model/multi-url-environment-no-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/multi-url-environment-no-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/JsonDeserializer.php b/seed/php-model/multi-url-environment-no-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/multi-url-environment-no-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/JsonEncoder.php b/seed/php-model/multi-url-environment-no-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/multi-url-environment-no-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/SerializableType.php b/seed/php-model/multi-url-environment-no-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/multi-url-environment-no-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Types/ArrayType.php b/seed/php-model/multi-url-environment-no-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Types/Constant.php b/seed/php-model/multi-url-environment-no-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Union.php b/seed/php-model/multi-url-environment-no-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/multi-url-environment-no-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/src/Core/Utils.php b/seed/php-model/multi-url-environment-no-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/multi-url-environment-no-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/EnumTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/TestTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/multi-url-environment/src/Core/ArrayType.php b/seed/php-model/multi-url-environment/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/multi-url-environment/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/multi-url-environment/src/Core/Constant.php b/seed/php-model/multi-url-environment/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/multi-url-environment/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/multi-url-environment/src/Core/Json/JsonDeserializer.php b/seed/php-model/multi-url-environment/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/multi-url-environment/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/multi-url-environment/src/Core/Json/JsonEncoder.php b/seed/php-model/multi-url-environment/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/multi-url-environment/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/multi-url-environment/src/Core/Json/SerializableType.php b/seed/php-model/multi-url-environment/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/multi-url-environment/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/multi-url-environment/src/Core/Json/Utils.php b/seed/php-model/multi-url-environment/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/multi-url-environment/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/multi-url-environment/src/Core/JsonDecoder.php b/seed/php-model/multi-url-environment/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/multi-url-environment/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/multi-url-environment/src/Core/JsonDeserializer.php b/seed/php-model/multi-url-environment/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/multi-url-environment/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/multi-url-environment/src/Core/JsonEncoder.php b/seed/php-model/multi-url-environment/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/multi-url-environment/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/multi-url-environment/src/Core/SerializableType.php b/seed/php-model/multi-url-environment/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/multi-url-environment/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/multi-url-environment/src/Core/Types/ArrayType.php b/seed/php-model/multi-url-environment/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/multi-url-environment/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/multi-url-environment/src/Core/Types/Constant.php b/seed/php-model/multi-url-environment/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/multi-url-environment/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/multi-url-environment/src/Core/Union.php b/seed/php-model/multi-url-environment/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/multi-url-environment/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/multi-url-environment/src/Core/Utils.php b/seed/php-model/multi-url-environment/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/multi-url-environment/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/EnumTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/TestTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/multi-url-environment/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/no-environment/src/Core/ArrayType.php b/seed/php-model/no-environment/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/no-environment/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/no-environment/src/Core/Constant.php b/seed/php-model/no-environment/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/no-environment/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/no-environment/src/Core/Json/JsonDeserializer.php b/seed/php-model/no-environment/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/no-environment/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/no-environment/src/Core/Json/JsonEncoder.php b/seed/php-model/no-environment/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/no-environment/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/no-environment/src/Core/Json/SerializableType.php b/seed/php-model/no-environment/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/no-environment/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/no-environment/src/Core/Json/Utils.php b/seed/php-model/no-environment/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/no-environment/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/no-environment/src/Core/JsonDecoder.php b/seed/php-model/no-environment/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/no-environment/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/no-environment/src/Core/JsonDeserializer.php b/seed/php-model/no-environment/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/no-environment/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/no-environment/src/Core/JsonEncoder.php b/seed/php-model/no-environment/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/no-environment/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/no-environment/src/Core/SerializableType.php b/seed/php-model/no-environment/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/no-environment/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/no-environment/src/Core/Types/ArrayType.php b/seed/php-model/no-environment/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/no-environment/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/no-environment/src/Core/Types/Constant.php b/seed/php-model/no-environment/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/no-environment/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/no-environment/src/Core/Union.php b/seed/php-model/no-environment/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/no-environment/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/no-environment/src/Core/Utils.php b/seed/php-model/no-environment/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/no-environment/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/no-environment/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/EnumTest.php b/seed/php-model/no-environment/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/no-environment/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/no-environment/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/TestTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/src/Auth/TokenResponse.php b/seed/php-model/oauth-client-credentials-default/src/Auth/TokenResponse.php index 0ba3caa2207..46d45bdc55c 100644 --- a/seed/php-model/oauth-client-credentials-default/src/Auth/TokenResponse.php +++ b/seed/php-model/oauth-client-credentials-default/src/Auth/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/ArrayType.php b/seed/php-model/oauth-client-credentials-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/oauth-client-credentials-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Constant.php b/seed/php-model/oauth-client-credentials-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/oauth-client-credentials-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonEncoder.php b/seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Json/SerializableType.php b/seed/php-model/oauth-client-credentials-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Json/Utils.php b/seed/php-model/oauth-client-credentials-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/JsonDecoder.php b/seed/php-model/oauth-client-credentials-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/oauth-client-credentials-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/oauth-client-credentials-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/JsonEncoder.php b/seed/php-model/oauth-client-credentials-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/oauth-client-credentials-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/SerializableType.php b/seed/php-model/oauth-client-credentials-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/oauth-client-credentials-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Types/ArrayType.php b/seed/php-model/oauth-client-credentials-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Types/Constant.php b/seed/php-model/oauth-client-credentials-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Union.php b/seed/php-model/oauth-client-credentials-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/oauth-client-credentials-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/src/Core/Utils.php b/seed/php-model/oauth-client-credentials-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/oauth-client-credentials-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/EnumTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/TestTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Auth/TokenResponse.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Auth/TokenResponse.php index 5f67f246612..8f67067fe38 100644 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Auth/TokenResponse.php +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Auth/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/ArrayType.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Constant.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonEncoder.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/SerializableType.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/Utils.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonEncoder.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/SerializableType.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/ArrayType.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/Constant.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Union.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Utils.php b/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/EnumTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/TestTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Auth/TokenResponse.php b/seed/php-model/oauth-client-credentials-nested-root/src/Auth/TokenResponse.php index 5f67f246612..8f67067fe38 100644 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Auth/TokenResponse.php +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Auth/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/ArrayType.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Constant.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonEncoder.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/SerializableType.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/Utils.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonEncoder.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/SerializableType.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/ArrayType.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/Constant.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Union.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Utils.php b/seed/php-model/oauth-client-credentials-nested-root/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/EnumTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/TestTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/src/Auth/TokenResponse.php b/seed/php-model/oauth-client-credentials/src/Auth/TokenResponse.php index 5f67f246612..8f67067fe38 100644 --- a/seed/php-model/oauth-client-credentials/src/Auth/TokenResponse.php +++ b/seed/php-model/oauth-client-credentials/src/Auth/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-model/oauth-client-credentials/src/Core/ArrayType.php b/seed/php-model/oauth-client-credentials/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/oauth-client-credentials/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Constant.php b/seed/php-model/oauth-client-credentials/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/oauth-client-credentials/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Json/JsonDeserializer.php b/seed/php-model/oauth-client-credentials/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Json/JsonEncoder.php b/seed/php-model/oauth-client-credentials/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Json/SerializableType.php b/seed/php-model/oauth-client-credentials/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Json/Utils.php b/seed/php-model/oauth-client-credentials/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/oauth-client-credentials/src/Core/JsonDecoder.php b/seed/php-model/oauth-client-credentials/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/oauth-client-credentials/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/oauth-client-credentials/src/Core/JsonDeserializer.php b/seed/php-model/oauth-client-credentials/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/oauth-client-credentials/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/oauth-client-credentials/src/Core/JsonEncoder.php b/seed/php-model/oauth-client-credentials/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/oauth-client-credentials/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/oauth-client-credentials/src/Core/SerializableType.php b/seed/php-model/oauth-client-credentials/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/oauth-client-credentials/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Types/ArrayType.php b/seed/php-model/oauth-client-credentials/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/oauth-client-credentials/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Types/Constant.php b/seed/php-model/oauth-client-credentials/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/oauth-client-credentials/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Union.php b/seed/php-model/oauth-client-credentials/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/oauth-client-credentials/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/oauth-client-credentials/src/Core/Utils.php b/seed/php-model/oauth-client-credentials/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/oauth-client-credentials/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/EnumTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/TestTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/object/src/Core/ArrayType.php b/seed/php-model/object/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/object/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/object/src/Core/Constant.php b/seed/php-model/object/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/object/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/object/src/Core/Json/JsonDeserializer.php b/seed/php-model/object/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/object/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/object/src/Core/Json/JsonEncoder.php b/seed/php-model/object/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/object/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/object/src/Core/Json/SerializableType.php b/seed/php-model/object/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/object/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/object/src/Core/Json/Utils.php b/seed/php-model/object/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/object/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/object/src/Core/JsonDecoder.php b/seed/php-model/object/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/object/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/object/src/Core/JsonDeserializer.php b/seed/php-model/object/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/object/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/object/src/Core/JsonEncoder.php b/seed/php-model/object/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/object/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/object/src/Core/SerializableType.php b/seed/php-model/object/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/object/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/object/src/Core/Types/ArrayType.php b/seed/php-model/object/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/object/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/object/src/Core/Types/Constant.php b/seed/php-model/object/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/object/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/object/src/Core/Union.php b/seed/php-model/object/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/object/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/object/src/Core/Utils.php b/seed/php-model/object/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/object/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/object/src/Name.php b/seed/php-model/object/src/Name.php index e0ba2a5229e..9030c74778d 100644 --- a/seed/php-model/object/src/Name.php +++ b/seed/php-model/object/src/Name.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Name extends SerializableType { diff --git a/seed/php-model/object/src/Type.php b/seed/php-model/object/src/Type.php index 80b762719e4..a305eeaeaf3 100644 --- a/seed/php-model/object/src/Type.php +++ b/seed/php-model/object/src/Type.php @@ -2,12 +2,12 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use DateTime; -use Seed\Core\DateType; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Types\DateType; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; /** * Exercises all of the built-in types. diff --git a/seed/php-model/object/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/object/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/object/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/object/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/EnumTest.php b/seed/php-model/object/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/object/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/object/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/object/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/object/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/object/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/object/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/object/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/object/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/object/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/object/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/object/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/object/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/object/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/object/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/object/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/object/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/object/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/TestTypeTest.php b/seed/php-model/object/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/object/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/object/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/object/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/object/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/object/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/object/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/objects-with-imports/src/Commons/Metadata/Metadata.php b/seed/php-model/objects-with-imports/src/Commons/Metadata/Metadata.php index a5c34a6f684..ccf1aaa9606 100644 --- a/seed/php-model/objects-with-imports/src/Commons/Metadata/Metadata.php +++ b/seed/php-model/objects-with-imports/src/Commons/Metadata/Metadata.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Metadata; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Metadata extends SerializableType { diff --git a/seed/php-model/objects-with-imports/src/Core/ArrayType.php b/seed/php-model/objects-with-imports/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/objects-with-imports/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/objects-with-imports/src/Core/Constant.php b/seed/php-model/objects-with-imports/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/objects-with-imports/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/objects-with-imports/src/Core/Json/JsonDeserializer.php b/seed/php-model/objects-with-imports/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/objects-with-imports/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/objects-with-imports/src/Core/Json/JsonEncoder.php b/seed/php-model/objects-with-imports/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/objects-with-imports/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/objects-with-imports/src/Core/Json/SerializableType.php b/seed/php-model/objects-with-imports/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/objects-with-imports/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/objects-with-imports/src/Core/Json/Utils.php b/seed/php-model/objects-with-imports/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/objects-with-imports/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/objects-with-imports/src/Core/JsonDecoder.php b/seed/php-model/objects-with-imports/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/objects-with-imports/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/objects-with-imports/src/Core/JsonDeserializer.php b/seed/php-model/objects-with-imports/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/objects-with-imports/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/objects-with-imports/src/Core/JsonEncoder.php b/seed/php-model/objects-with-imports/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/objects-with-imports/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/objects-with-imports/src/Core/SerializableType.php b/seed/php-model/objects-with-imports/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/objects-with-imports/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/objects-with-imports/src/Core/Types/ArrayType.php b/seed/php-model/objects-with-imports/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/objects-with-imports/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/objects-with-imports/src/Core/Types/Constant.php b/seed/php-model/objects-with-imports/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/objects-with-imports/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/objects-with-imports/src/Core/Union.php b/seed/php-model/objects-with-imports/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/objects-with-imports/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/objects-with-imports/src/Core/Utils.php b/seed/php-model/objects-with-imports/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/objects-with-imports/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/objects-with-imports/src/File/Directory/Directory.php b/seed/php-model/objects-with-imports/src/File/Directory/Directory.php index ca3f38cea25..0aaa22f1826 100644 --- a/seed/php-model/objects-with-imports/src/File/Directory/Directory.php +++ b/seed/php-model/objects-with-imports/src/File/Directory/Directory.php @@ -2,10 +2,10 @@ namespace Seed\File\Directory; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\File\File; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class Directory extends SerializableType { diff --git a/seed/php-model/objects-with-imports/src/File/File.php b/seed/php-model/objects-with-imports/src/File/File.php index 8531c31fe29..933c9c35b71 100644 --- a/seed/php-model/objects-with-imports/src/File/File.php +++ b/seed/php-model/objects-with-imports/src/File/File.php @@ -2,8 +2,8 @@ namespace Seed\File; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class File extends SerializableType { diff --git a/seed/php-model/objects-with-imports/src/Node.php b/seed/php-model/objects-with-imports/src/Node.php index 5999843a0fe..79d36017922 100644 --- a/seed/php-model/objects-with-imports/src/Node.php +++ b/seed/php-model/objects-with-imports/src/Node.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Metadata\Metadata; class Node extends SerializableType diff --git a/seed/php-model/objects-with-imports/src/Tree.php b/seed/php-model/objects-with-imports/src/Tree.php index 3d2e990ba93..a3d02635bc4 100644 --- a/seed/php-model/objects-with-imports/src/Tree.php +++ b/seed/php-model/objects-with-imports/src/Tree.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Tree extends SerializableType { diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/EnumTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/TestTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/optional/src/Core/ArrayType.php b/seed/php-model/optional/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/optional/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/optional/src/Core/Constant.php b/seed/php-model/optional/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/optional/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/optional/src/Core/Json/JsonDeserializer.php b/seed/php-model/optional/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/optional/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/optional/src/Core/Json/JsonEncoder.php b/seed/php-model/optional/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/optional/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/optional/src/Core/Json/SerializableType.php b/seed/php-model/optional/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/optional/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/optional/src/Core/Json/Utils.php b/seed/php-model/optional/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/optional/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/optional/src/Core/JsonDecoder.php b/seed/php-model/optional/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/optional/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/optional/src/Core/JsonDeserializer.php b/seed/php-model/optional/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/optional/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/optional/src/Core/JsonEncoder.php b/seed/php-model/optional/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/optional/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/optional/src/Core/SerializableType.php b/seed/php-model/optional/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/optional/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/optional/src/Core/Types/ArrayType.php b/seed/php-model/optional/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/optional/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/optional/src/Core/Types/Constant.php b/seed/php-model/optional/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/optional/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/optional/src/Core/Union.php b/seed/php-model/optional/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/optional/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/optional/src/Core/Utils.php b/seed/php-model/optional/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/optional/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/optional/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/EnumTest.php b/seed/php-model/optional/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/optional/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/optional/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/optional/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/optional/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/optional/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/optional/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/optional/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/optional/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/optional/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/optional/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/TestTypeTest.php b/seed/php-model/optional/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/optional/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/optional/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/optional/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/optional/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/package-yml/src/Core/ArrayType.php b/seed/php-model/package-yml/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/package-yml/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/package-yml/src/Core/Constant.php b/seed/php-model/package-yml/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/package-yml/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/package-yml/src/Core/Json/JsonDeserializer.php b/seed/php-model/package-yml/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/package-yml/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/package-yml/src/Core/Json/JsonEncoder.php b/seed/php-model/package-yml/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/package-yml/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/package-yml/src/Core/Json/SerializableType.php b/seed/php-model/package-yml/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/package-yml/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/package-yml/src/Core/Json/Utils.php b/seed/php-model/package-yml/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/package-yml/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/package-yml/src/Core/JsonDecoder.php b/seed/php-model/package-yml/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/package-yml/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/package-yml/src/Core/JsonDeserializer.php b/seed/php-model/package-yml/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/package-yml/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/package-yml/src/Core/JsonEncoder.php b/seed/php-model/package-yml/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/package-yml/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/package-yml/src/Core/SerializableType.php b/seed/php-model/package-yml/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/package-yml/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/package-yml/src/Core/Types/ArrayType.php b/seed/php-model/package-yml/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/package-yml/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/package-yml/src/Core/Types/Constant.php b/seed/php-model/package-yml/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/package-yml/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/package-yml/src/Core/Union.php b/seed/php-model/package-yml/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/package-yml/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/package-yml/src/Core/Utils.php b/seed/php-model/package-yml/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/package-yml/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/package-yml/src/EchoRequest.php b/seed/php-model/package-yml/src/EchoRequest.php index 4660c72afc3..18cb6a5de13 100644 --- a/seed/php-model/package-yml/src/EchoRequest.php +++ b/seed/php-model/package-yml/src/EchoRequest.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class EchoRequest extends SerializableType { diff --git a/seed/php-model/package-yml/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/package-yml/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/EnumTest.php b/seed/php-model/package-yml/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/package-yml/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/package-yml/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/TestTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/pagination/src/Core/ArrayType.php b/seed/php-model/pagination/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/pagination/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/pagination/src/Core/Constant.php b/seed/php-model/pagination/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/pagination/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/pagination/src/Core/Json/JsonDeserializer.php b/seed/php-model/pagination/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/pagination/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/pagination/src/Core/Json/JsonEncoder.php b/seed/php-model/pagination/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/pagination/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/pagination/src/Core/Json/SerializableType.php b/seed/php-model/pagination/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/pagination/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/pagination/src/Core/Json/Utils.php b/seed/php-model/pagination/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/pagination/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/pagination/src/Core/JsonDecoder.php b/seed/php-model/pagination/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/pagination/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/pagination/src/Core/JsonDeserializer.php b/seed/php-model/pagination/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/pagination/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/pagination/src/Core/JsonEncoder.php b/seed/php-model/pagination/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/pagination/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/pagination/src/Core/SerializableType.php b/seed/php-model/pagination/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/pagination/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/pagination/src/Core/Types/ArrayType.php b/seed/php-model/pagination/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/pagination/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/pagination/src/Core/Types/Constant.php b/seed/php-model/pagination/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/pagination/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/pagination/src/Core/Union.php b/seed/php-model/pagination/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/pagination/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/pagination/src/Core/Utils.php b/seed/php-model/pagination/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/pagination/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/pagination/src/UsernameCursor.php b/seed/php-model/pagination/src/UsernameCursor.php index 9df7a229864..4b63e1f5233 100644 --- a/seed/php-model/pagination/src/UsernameCursor.php +++ b/seed/php-model/pagination/src/UsernameCursor.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UsernameCursor extends SerializableType { diff --git a/seed/php-model/pagination/src/UsernamePage.php b/seed/php-model/pagination/src/UsernamePage.php index cfd530a0e32..f1493154aa4 100644 --- a/seed/php-model/pagination/src/UsernamePage.php +++ b/seed/php-model/pagination/src/UsernamePage.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UsernamePage extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/ListUsersExtendedOptionalListResponse.php b/seed/php-model/pagination/src/Users/ListUsersExtendedOptionalListResponse.php index 9bf224cf8ad..00810b43f46 100644 --- a/seed/php-model/pagination/src/Users/ListUsersExtendedOptionalListResponse.php +++ b/seed/php-model/pagination/src/Users/ListUsersExtendedOptionalListResponse.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ListUsersExtendedOptionalListResponse extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/ListUsersExtendedResponse.php b/seed/php-model/pagination/src/Users/ListUsersExtendedResponse.php index ffad279ace5..dc600ccd164 100644 --- a/seed/php-model/pagination/src/Users/ListUsersExtendedResponse.php +++ b/seed/php-model/pagination/src/Users/ListUsersExtendedResponse.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ListUsersExtendedResponse extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/ListUsersPaginationResponse.php b/seed/php-model/pagination/src/Users/ListUsersPaginationResponse.php index 29ff1f504d0..71c899098fb 100644 --- a/seed/php-model/pagination/src/Users/ListUsersPaginationResponse.php +++ b/seed/php-model/pagination/src/Users/ListUsersPaginationResponse.php @@ -2,9 +2,9 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ListUsersPaginationResponse extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/NextPage.php b/seed/php-model/pagination/src/Users/NextPage.php index 4e703c39568..7665d37add6 100644 --- a/seed/php-model/pagination/src/Users/NextPage.php +++ b/seed/php-model/pagination/src/Users/NextPage.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NextPage extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/Page.php b/seed/php-model/pagination/src/Users/Page.php index 962f4fefdab..8aea0567dd4 100644 --- a/seed/php-model/pagination/src/Users/Page.php +++ b/seed/php-model/pagination/src/Users/Page.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Page extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/User.php b/seed/php-model/pagination/src/Users/User.php index 611b517abef..3bf1fdef115 100644 --- a/seed/php-model/pagination/src/Users/User.php +++ b/seed/php-model/pagination/src/Users/User.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/UserListContainer.php b/seed/php-model/pagination/src/Users/UserListContainer.php index 8983b5e79d9..0055b5a007a 100644 --- a/seed/php-model/pagination/src/Users/UserListContainer.php +++ b/seed/php-model/pagination/src/Users/UserListContainer.php @@ -2,9 +2,9 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UserListContainer extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/UserOptionalListContainer.php b/seed/php-model/pagination/src/Users/UserOptionalListContainer.php index 6b267f2e6d4..167df5ff667 100644 --- a/seed/php-model/pagination/src/Users/UserOptionalListContainer.php +++ b/seed/php-model/pagination/src/Users/UserOptionalListContainer.php @@ -2,9 +2,9 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UserOptionalListContainer extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/UserOptionalListPage.php b/seed/php-model/pagination/src/Users/UserOptionalListPage.php index b88c793f89f..4c15869a2de 100644 --- a/seed/php-model/pagination/src/Users/UserOptionalListPage.php +++ b/seed/php-model/pagination/src/Users/UserOptionalListPage.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UserOptionalListPage extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/UserPage.php b/seed/php-model/pagination/src/Users/UserPage.php index 2dde1fcc4c5..e5371955c95 100644 --- a/seed/php-model/pagination/src/Users/UserPage.php +++ b/seed/php-model/pagination/src/Users/UserPage.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UserPage extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/UsernameContainer.php b/seed/php-model/pagination/src/Users/UsernameContainer.php index 33df67d9799..e941d65c22d 100644 --- a/seed/php-model/pagination/src/Users/UsernameContainer.php +++ b/seed/php-model/pagination/src/Users/UsernameContainer.php @@ -2,9 +2,9 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UsernameContainer extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/WithCursor.php b/seed/php-model/pagination/src/Users/WithCursor.php index 42a50ce0cde..2c102ba4cf8 100644 --- a/seed/php-model/pagination/src/Users/WithCursor.php +++ b/seed/php-model/pagination/src/Users/WithCursor.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WithCursor extends SerializableType { diff --git a/seed/php-model/pagination/src/Users/WithPage.php b/seed/php-model/pagination/src/Users/WithPage.php index 1564bc65232..d0facae2941 100644 --- a/seed/php-model/pagination/src/Users/WithPage.php +++ b/seed/php-model/pagination/src/Users/WithPage.php @@ -2,8 +2,8 @@ namespace Seed\Users; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WithPage extends SerializableType { diff --git a/seed/php-model/pagination/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/pagination/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/EnumTest.php b/seed/php-model/pagination/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/pagination/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/pagination/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/pagination/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/pagination/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/TestTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/pagination/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/pagination/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/pagination/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/plain-text/src/Core/ArrayType.php b/seed/php-model/plain-text/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/plain-text/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/plain-text/src/Core/Constant.php b/seed/php-model/plain-text/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/plain-text/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/plain-text/src/Core/Json/JsonDeserializer.php b/seed/php-model/plain-text/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/plain-text/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/plain-text/src/Core/Json/JsonEncoder.php b/seed/php-model/plain-text/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/plain-text/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/plain-text/src/Core/Json/SerializableType.php b/seed/php-model/plain-text/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/plain-text/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/plain-text/src/Core/Json/Utils.php b/seed/php-model/plain-text/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/plain-text/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/plain-text/src/Core/JsonDecoder.php b/seed/php-model/plain-text/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/plain-text/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/plain-text/src/Core/JsonDeserializer.php b/seed/php-model/plain-text/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/plain-text/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/plain-text/src/Core/JsonEncoder.php b/seed/php-model/plain-text/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/plain-text/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/plain-text/src/Core/SerializableType.php b/seed/php-model/plain-text/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/plain-text/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/plain-text/src/Core/Types/ArrayType.php b/seed/php-model/plain-text/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/plain-text/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/plain-text/src/Core/Types/Constant.php b/seed/php-model/plain-text/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/plain-text/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/plain-text/src/Core/Union.php b/seed/php-model/plain-text/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/plain-text/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/plain-text/src/Core/Utils.php b/seed/php-model/plain-text/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/plain-text/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/plain-text/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/EnumTest.php b/seed/php-model/plain-text/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/plain-text/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/plain-text/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/TestTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/query-parameters/src/Core/ArrayType.php b/seed/php-model/query-parameters/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/query-parameters/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/query-parameters/src/Core/Constant.php b/seed/php-model/query-parameters/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/query-parameters/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/query-parameters/src/Core/Json/JsonDeserializer.php b/seed/php-model/query-parameters/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/query-parameters/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/query-parameters/src/Core/Json/JsonEncoder.php b/seed/php-model/query-parameters/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/query-parameters/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/query-parameters/src/Core/Json/SerializableType.php b/seed/php-model/query-parameters/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/query-parameters/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/query-parameters/src/Core/Json/Utils.php b/seed/php-model/query-parameters/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/query-parameters/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/query-parameters/src/Core/JsonDecoder.php b/seed/php-model/query-parameters/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/query-parameters/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/query-parameters/src/Core/JsonDeserializer.php b/seed/php-model/query-parameters/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/query-parameters/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/query-parameters/src/Core/JsonEncoder.php b/seed/php-model/query-parameters/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/query-parameters/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/query-parameters/src/Core/SerializableType.php b/seed/php-model/query-parameters/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/query-parameters/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/query-parameters/src/Core/Types/ArrayType.php b/seed/php-model/query-parameters/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/query-parameters/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/query-parameters/src/Core/Types/Constant.php b/seed/php-model/query-parameters/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/query-parameters/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/query-parameters/src/Core/Union.php b/seed/php-model/query-parameters/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/query-parameters/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/query-parameters/src/Core/Utils.php b/seed/php-model/query-parameters/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/query-parameters/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/query-parameters/src/User/NestedUser.php b/seed/php-model/query-parameters/src/User/NestedUser.php index a7ba21d9bb6..47bda687295 100644 --- a/seed/php-model/query-parameters/src/User/NestedUser.php +++ b/seed/php-model/query-parameters/src/User/NestedUser.php @@ -2,8 +2,8 @@ namespace Seed\User; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedUser extends SerializableType { diff --git a/seed/php-model/query-parameters/src/User/User.php b/seed/php-model/query-parameters/src/User/User.php index 139981c22d0..8ce32968d0e 100644 --- a/seed/php-model/query-parameters/src/User/User.php +++ b/seed/php-model/query-parameters/src/User/User.php @@ -2,9 +2,9 @@ namespace Seed\User; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class User extends SerializableType { diff --git a/seed/php-model/query-parameters/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/query-parameters/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/EnumTest.php b/seed/php-model/query-parameters/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/query-parameters/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/query-parameters/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/TestTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/reserved-keywords/src/Core/ArrayType.php b/seed/php-model/reserved-keywords/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/reserved-keywords/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/reserved-keywords/src/Core/Constant.php b/seed/php-model/reserved-keywords/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/reserved-keywords/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/reserved-keywords/src/Core/Json/JsonDeserializer.php b/seed/php-model/reserved-keywords/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/reserved-keywords/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/reserved-keywords/src/Core/Json/JsonEncoder.php b/seed/php-model/reserved-keywords/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/reserved-keywords/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/reserved-keywords/src/Core/Json/SerializableType.php b/seed/php-model/reserved-keywords/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/reserved-keywords/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/reserved-keywords/src/Core/Json/Utils.php b/seed/php-model/reserved-keywords/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/reserved-keywords/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/reserved-keywords/src/Core/JsonDecoder.php b/seed/php-model/reserved-keywords/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/reserved-keywords/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/reserved-keywords/src/Core/JsonDeserializer.php b/seed/php-model/reserved-keywords/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/reserved-keywords/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/reserved-keywords/src/Core/JsonEncoder.php b/seed/php-model/reserved-keywords/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/reserved-keywords/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/reserved-keywords/src/Core/SerializableType.php b/seed/php-model/reserved-keywords/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/reserved-keywords/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/reserved-keywords/src/Core/Types/ArrayType.php b/seed/php-model/reserved-keywords/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/reserved-keywords/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/reserved-keywords/src/Core/Types/Constant.php b/seed/php-model/reserved-keywords/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/reserved-keywords/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/reserved-keywords/src/Core/Union.php b/seed/php-model/reserved-keywords/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/reserved-keywords/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/reserved-keywords/src/Core/Utils.php b/seed/php-model/reserved-keywords/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/reserved-keywords/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/reserved-keywords/src/Package/Package.php b/seed/php-model/reserved-keywords/src/Package/Package.php index 4e36d8ee0ee..397cac2272e 100644 --- a/seed/php-model/reserved-keywords/src/Package/Package.php +++ b/seed/php-model/reserved-keywords/src/Package/Package.php @@ -2,8 +2,8 @@ namespace Seed\Package; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Package extends SerializableType { diff --git a/seed/php-model/reserved-keywords/src/Package/Record.php b/seed/php-model/reserved-keywords/src/Package/Record.php index b390babf833..a70a8bffc8c 100644 --- a/seed/php-model/reserved-keywords/src/Package/Record.php +++ b/seed/php-model/reserved-keywords/src/Package/Record.php @@ -2,9 +2,9 @@ namespace Seed\Package; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Record extends SerializableType { diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/EnumTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/TestTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/response-property/src/Core/ArrayType.php b/seed/php-model/response-property/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/response-property/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/response-property/src/Core/Constant.php b/seed/php-model/response-property/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/response-property/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/response-property/src/Core/Json/JsonDeserializer.php b/seed/php-model/response-property/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/response-property/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/response-property/src/Core/Json/JsonEncoder.php b/seed/php-model/response-property/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/response-property/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/response-property/src/Core/Json/SerializableType.php b/seed/php-model/response-property/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/response-property/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/response-property/src/Core/Json/Utils.php b/seed/php-model/response-property/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/response-property/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/response-property/src/Core/JsonDecoder.php b/seed/php-model/response-property/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/response-property/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/response-property/src/Core/JsonDeserializer.php b/seed/php-model/response-property/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/response-property/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/response-property/src/Core/JsonEncoder.php b/seed/php-model/response-property/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/response-property/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/response-property/src/Core/SerializableType.php b/seed/php-model/response-property/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/response-property/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/response-property/src/Core/Types/ArrayType.php b/seed/php-model/response-property/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/response-property/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/response-property/src/Core/Types/Constant.php b/seed/php-model/response-property/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/response-property/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/response-property/src/Core/Union.php b/seed/php-model/response-property/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/response-property/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/response-property/src/Core/Utils.php b/seed/php-model/response-property/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/response-property/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/response-property/src/Service/Movie.php b/seed/php-model/response-property/src/Service/Movie.php index 7d67bb018be..eb2feb49bf3 100644 --- a/seed/php-model/response-property/src/Service/Movie.php +++ b/seed/php-model/response-property/src/Service/Movie.php @@ -2,8 +2,8 @@ namespace Seed\Service; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Movie extends SerializableType { diff --git a/seed/php-model/response-property/src/Service/Response.php b/seed/php-model/response-property/src/Service/Response.php index 16708743c45..46eaf9deee5 100644 --- a/seed/php-model/response-property/src/Service/Response.php +++ b/seed/php-model/response-property/src/Service/Response.php @@ -2,8 +2,8 @@ namespace Seed\Service; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-model/response-property/src/Service/WithDocs.php b/seed/php-model/response-property/src/Service/WithDocs.php index a1dd30e9433..777a0315ea4 100644 --- a/seed/php-model/response-property/src/Service/WithDocs.php +++ b/seed/php-model/response-property/src/Service/WithDocs.php @@ -2,8 +2,8 @@ namespace Seed\Service; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WithDocs extends SerializableType { diff --git a/seed/php-model/response-property/src/StringResponse.php b/seed/php-model/response-property/src/StringResponse.php index ee79e9f2e60..658e40cc43e 100644 --- a/seed/php-model/response-property/src/StringResponse.php +++ b/seed/php-model/response-property/src/StringResponse.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StringResponse extends SerializableType { diff --git a/seed/php-model/response-property/src/WithMetadata.php b/seed/php-model/response-property/src/WithMetadata.php index 9ac01ba39ae..7e06f37d521 100644 --- a/seed/php-model/response-property/src/WithMetadata.php +++ b/seed/php-model/response-property/src/WithMetadata.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WithMetadata extends SerializableType { diff --git a/seed/php-model/response-property/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/response-property/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/EnumTest.php b/seed/php-model/response-property/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/response-property/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/response-property/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/response-property/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/TestTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/response-property/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/response-property/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/response-property/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/src/Completions/StreamedCompletion.php b/seed/php-model/server-sent-event-examples/src/Completions/StreamedCompletion.php index 59208a9fc57..f18622ed7cb 100644 --- a/seed/php-model/server-sent-event-examples/src/Completions/StreamedCompletion.php +++ b/seed/php-model/server-sent-event-examples/src/Completions/StreamedCompletion.php @@ -2,8 +2,8 @@ namespace Seed\Completions; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamedCompletion extends SerializableType { diff --git a/seed/php-model/server-sent-event-examples/src/Core/ArrayType.php b/seed/php-model/server-sent-event-examples/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/server-sent-event-examples/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Constant.php b/seed/php-model/server-sent-event-examples/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/server-sent-event-examples/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Json/JsonDeserializer.php b/seed/php-model/server-sent-event-examples/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Json/JsonEncoder.php b/seed/php-model/server-sent-event-examples/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Json/SerializableType.php b/seed/php-model/server-sent-event-examples/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Json/Utils.php b/seed/php-model/server-sent-event-examples/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/JsonDecoder.php b/seed/php-model/server-sent-event-examples/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/server-sent-event-examples/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/server-sent-event-examples/src/Core/JsonDeserializer.php b/seed/php-model/server-sent-event-examples/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/server-sent-event-examples/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/server-sent-event-examples/src/Core/JsonEncoder.php b/seed/php-model/server-sent-event-examples/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/server-sent-event-examples/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/server-sent-event-examples/src/Core/SerializableType.php b/seed/php-model/server-sent-event-examples/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/server-sent-event-examples/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Types/ArrayType.php b/seed/php-model/server-sent-event-examples/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Types/Constant.php b/seed/php-model/server-sent-event-examples/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/server-sent-event-examples/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Union.php b/seed/php-model/server-sent-event-examples/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/server-sent-event-examples/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/server-sent-event-examples/src/Core/Utils.php b/seed/php-model/server-sent-event-examples/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/server-sent-event-examples/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/EnumTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/server-sent-events/src/Completions/StreamedCompletion.php b/seed/php-model/server-sent-events/src/Completions/StreamedCompletion.php index 59208a9fc57..f18622ed7cb 100644 --- a/seed/php-model/server-sent-events/src/Completions/StreamedCompletion.php +++ b/seed/php-model/server-sent-events/src/Completions/StreamedCompletion.php @@ -2,8 +2,8 @@ namespace Seed\Completions; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamedCompletion extends SerializableType { diff --git a/seed/php-model/server-sent-events/src/Core/ArrayType.php b/seed/php-model/server-sent-events/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/server-sent-events/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/server-sent-events/src/Core/Constant.php b/seed/php-model/server-sent-events/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/server-sent-events/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Json/JsonDeserializer.php b/seed/php-model/server-sent-events/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Json/JsonEncoder.php b/seed/php-model/server-sent-events/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Json/SerializableType.php b/seed/php-model/server-sent-events/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Json/Utils.php b/seed/php-model/server-sent-events/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/server-sent-events/src/Core/JsonDecoder.php b/seed/php-model/server-sent-events/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/server-sent-events/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/server-sent-events/src/Core/JsonDeserializer.php b/seed/php-model/server-sent-events/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/server-sent-events/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/server-sent-events/src/Core/JsonEncoder.php b/seed/php-model/server-sent-events/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/server-sent-events/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/server-sent-events/src/Core/SerializableType.php b/seed/php-model/server-sent-events/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/server-sent-events/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/server-sent-events/src/Core/Types/ArrayType.php b/seed/php-model/server-sent-events/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Types/Constant.php b/seed/php-model/server-sent-events/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/server-sent-events/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/server-sent-events/src/Core/Union.php b/seed/php-model/server-sent-events/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/server-sent-events/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/server-sent-events/src/Core/Utils.php b/seed/php-model/server-sent-events/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/server-sent-events/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/EnumTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/TestTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/simple-fhir/src/Account.php b/seed/php-model/simple-fhir/src/Account.php index 7c50f6a57af..d845b0822f6 100644 --- a/seed/php-model/simple-fhir/src/Account.php +++ b/seed/php-model/simple-fhir/src/Account.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Account extends SerializableType { diff --git a/seed/php-model/simple-fhir/src/BaseResource.php b/seed/php-model/simple-fhir/src/BaseResource.php index ea9ceafd1db..c87d3baf585 100644 --- a/seed/php-model/simple-fhir/src/BaseResource.php +++ b/seed/php-model/simple-fhir/src/BaseResource.php @@ -2,10 +2,10 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class BaseResource extends SerializableType { diff --git a/seed/php-model/simple-fhir/src/Core/ArrayType.php b/seed/php-model/simple-fhir/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/simple-fhir/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/simple-fhir/src/Core/Constant.php b/seed/php-model/simple-fhir/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/simple-fhir/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/simple-fhir/src/Core/Json/JsonDeserializer.php b/seed/php-model/simple-fhir/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/simple-fhir/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/simple-fhir/src/Core/Json/JsonEncoder.php b/seed/php-model/simple-fhir/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/simple-fhir/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/simple-fhir/src/Core/Json/SerializableType.php b/seed/php-model/simple-fhir/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/simple-fhir/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/simple-fhir/src/Core/Json/Utils.php b/seed/php-model/simple-fhir/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/simple-fhir/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/simple-fhir/src/Core/JsonDecoder.php b/seed/php-model/simple-fhir/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/simple-fhir/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/simple-fhir/src/Core/JsonDeserializer.php b/seed/php-model/simple-fhir/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/simple-fhir/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/simple-fhir/src/Core/JsonEncoder.php b/seed/php-model/simple-fhir/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/simple-fhir/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/simple-fhir/src/Core/SerializableType.php b/seed/php-model/simple-fhir/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/simple-fhir/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/simple-fhir/src/Core/Types/ArrayType.php b/seed/php-model/simple-fhir/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/simple-fhir/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/simple-fhir/src/Core/Types/Constant.php b/seed/php-model/simple-fhir/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/simple-fhir/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/simple-fhir/src/Core/Union.php b/seed/php-model/simple-fhir/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/simple-fhir/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/simple-fhir/src/Core/Utils.php b/seed/php-model/simple-fhir/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/simple-fhir/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/simple-fhir/src/Memo.php b/seed/php-model/simple-fhir/src/Memo.php index ddf3dc98bfc..4e52b58fc9b 100644 --- a/seed/php-model/simple-fhir/src/Memo.php +++ b/seed/php-model/simple-fhir/src/Memo.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Memo extends SerializableType { diff --git a/seed/php-model/simple-fhir/src/Patient.php b/seed/php-model/simple-fhir/src/Patient.php index 87d967806c0..277551e5508 100644 --- a/seed/php-model/simple-fhir/src/Patient.php +++ b/seed/php-model/simple-fhir/src/Patient.php @@ -2,9 +2,9 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Patient extends SerializableType { diff --git a/seed/php-model/simple-fhir/src/Practitioner.php b/seed/php-model/simple-fhir/src/Practitioner.php index 90b29d87930..708896e88be 100644 --- a/seed/php-model/simple-fhir/src/Practitioner.php +++ b/seed/php-model/simple-fhir/src/Practitioner.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Practitioner extends SerializableType { diff --git a/seed/php-model/simple-fhir/src/Script.php b/seed/php-model/simple-fhir/src/Script.php index 5341f997731..088cbeaaad8 100644 --- a/seed/php-model/simple-fhir/src/Script.php +++ b/seed/php-model/simple-fhir/src/Script.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Script extends SerializableType { diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/EnumTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/simple-fhir/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/TestTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/single-url-environment-default/src/Core/ArrayType.php b/seed/php-model/single-url-environment-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/single-url-environment-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/single-url-environment-default/src/Core/Constant.php b/seed/php-model/single-url-environment-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/single-url-environment-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/single-url-environment-default/src/Core/Json/JsonDeserializer.php b/seed/php-model/single-url-environment-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/single-url-environment-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/single-url-environment-default/src/Core/Json/JsonEncoder.php b/seed/php-model/single-url-environment-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/single-url-environment-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/single-url-environment-default/src/Core/Json/SerializableType.php b/seed/php-model/single-url-environment-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/single-url-environment-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/single-url-environment-default/src/Core/Json/Utils.php b/seed/php-model/single-url-environment-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/single-url-environment-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/single-url-environment-default/src/Core/JsonDecoder.php b/seed/php-model/single-url-environment-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/single-url-environment-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/single-url-environment-default/src/Core/JsonDeserializer.php b/seed/php-model/single-url-environment-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/single-url-environment-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/single-url-environment-default/src/Core/JsonEncoder.php b/seed/php-model/single-url-environment-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/single-url-environment-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/single-url-environment-default/src/Core/SerializableType.php b/seed/php-model/single-url-environment-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/single-url-environment-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/single-url-environment-default/src/Core/Types/ArrayType.php b/seed/php-model/single-url-environment-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/single-url-environment-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/single-url-environment-default/src/Core/Types/Constant.php b/seed/php-model/single-url-environment-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/single-url-environment-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/single-url-environment-default/src/Core/Union.php b/seed/php-model/single-url-environment-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/single-url-environment-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/single-url-environment-default/src/Core/Utils.php b/seed/php-model/single-url-environment-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/single-url-environment-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/EnumTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/single-url-environment-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/TestTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/ArrayType.php b/seed/php-model/single-url-environment-no-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/single-url-environment-no-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Constant.php b/seed/php-model/single-url-environment-no-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/single-url-environment-no-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Json/JsonDeserializer.php b/seed/php-model/single-url-environment-no-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Json/JsonEncoder.php b/seed/php-model/single-url-environment-no-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Json/SerializableType.php b/seed/php-model/single-url-environment-no-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Json/Utils.php b/seed/php-model/single-url-environment-no-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/JsonDecoder.php b/seed/php-model/single-url-environment-no-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/single-url-environment-no-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/JsonDeserializer.php b/seed/php-model/single-url-environment-no-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/single-url-environment-no-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/JsonEncoder.php b/seed/php-model/single-url-environment-no-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/single-url-environment-no-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/SerializableType.php b/seed/php-model/single-url-environment-no-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/single-url-environment-no-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Types/ArrayType.php b/seed/php-model/single-url-environment-no-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Types/Constant.php b/seed/php-model/single-url-environment-no-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Union.php b/seed/php-model/single-url-environment-no-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/single-url-environment-no-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/single-url-environment-no-default/src/Core/Utils.php b/seed/php-model/single-url-environment-no-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/single-url-environment-no-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/EnumTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/TestTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/streaming-parameter/src/Core/ArrayType.php b/seed/php-model/streaming-parameter/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/streaming-parameter/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/streaming-parameter/src/Core/Constant.php b/seed/php-model/streaming-parameter/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/streaming-parameter/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/streaming-parameter/src/Core/Json/JsonDeserializer.php b/seed/php-model/streaming-parameter/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/streaming-parameter/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/streaming-parameter/src/Core/Json/JsonEncoder.php b/seed/php-model/streaming-parameter/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/streaming-parameter/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/streaming-parameter/src/Core/Json/SerializableType.php b/seed/php-model/streaming-parameter/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/streaming-parameter/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/streaming-parameter/src/Core/Json/Utils.php b/seed/php-model/streaming-parameter/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/streaming-parameter/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/streaming-parameter/src/Core/JsonDecoder.php b/seed/php-model/streaming-parameter/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/streaming-parameter/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/streaming-parameter/src/Core/JsonDeserializer.php b/seed/php-model/streaming-parameter/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/streaming-parameter/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/streaming-parameter/src/Core/JsonEncoder.php b/seed/php-model/streaming-parameter/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/streaming-parameter/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/streaming-parameter/src/Core/SerializableType.php b/seed/php-model/streaming-parameter/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/streaming-parameter/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/streaming-parameter/src/Core/Types/ArrayType.php b/seed/php-model/streaming-parameter/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/streaming-parameter/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/streaming-parameter/src/Core/Types/Constant.php b/seed/php-model/streaming-parameter/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/streaming-parameter/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/streaming-parameter/src/Core/Union.php b/seed/php-model/streaming-parameter/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/streaming-parameter/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/streaming-parameter/src/Core/Utils.php b/seed/php-model/streaming-parameter/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/streaming-parameter/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/streaming-parameter/src/Dummy/RegularResponse.php b/seed/php-model/streaming-parameter/src/Dummy/RegularResponse.php index 87a8ee412d3..c5aba21c0a7 100644 --- a/seed/php-model/streaming-parameter/src/Dummy/RegularResponse.php +++ b/seed/php-model/streaming-parameter/src/Dummy/RegularResponse.php @@ -2,8 +2,8 @@ namespace Seed\Dummy; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RegularResponse extends SerializableType { diff --git a/seed/php-model/streaming-parameter/src/Dummy/StreamResponse.php b/seed/php-model/streaming-parameter/src/Dummy/StreamResponse.php index 6201cee266a..7a62856a8b7 100644 --- a/seed/php-model/streaming-parameter/src/Dummy/StreamResponse.php +++ b/seed/php-model/streaming-parameter/src/Dummy/StreamResponse.php @@ -2,8 +2,8 @@ namespace Seed\Dummy; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamResponse extends SerializableType { diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/EnumTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/streaming-parameter/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/TestTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/streaming/src/Core/ArrayType.php b/seed/php-model/streaming/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/streaming/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/streaming/src/Core/Constant.php b/seed/php-model/streaming/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/streaming/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/streaming/src/Core/Json/JsonDeserializer.php b/seed/php-model/streaming/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/streaming/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/streaming/src/Core/Json/JsonEncoder.php b/seed/php-model/streaming/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/streaming/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/streaming/src/Core/Json/SerializableType.php b/seed/php-model/streaming/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/streaming/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/streaming/src/Core/Json/Utils.php b/seed/php-model/streaming/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/streaming/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/streaming/src/Core/JsonDecoder.php b/seed/php-model/streaming/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/streaming/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/streaming/src/Core/JsonDeserializer.php b/seed/php-model/streaming/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/streaming/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/streaming/src/Core/JsonEncoder.php b/seed/php-model/streaming/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/streaming/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/streaming/src/Core/SerializableType.php b/seed/php-model/streaming/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/streaming/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/streaming/src/Core/Types/ArrayType.php b/seed/php-model/streaming/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/streaming/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/streaming/src/Core/Types/Constant.php b/seed/php-model/streaming/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/streaming/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/streaming/src/Core/Union.php b/seed/php-model/streaming/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/streaming/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/streaming/src/Core/Utils.php b/seed/php-model/streaming/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/streaming/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/streaming/src/Dummy/StreamResponse.php b/seed/php-model/streaming/src/Dummy/StreamResponse.php index 6201cee266a..7a62856a8b7 100644 --- a/seed/php-model/streaming/src/Dummy/StreamResponse.php +++ b/seed/php-model/streaming/src/Dummy/StreamResponse.php @@ -2,8 +2,8 @@ namespace Seed\Dummy; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamResponse extends SerializableType { diff --git a/seed/php-model/streaming/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/streaming/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/EnumTest.php b/seed/php-model/streaming/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/streaming/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/streaming/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/streaming/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/streaming/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/TestTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/streaming/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/streaming/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/streaming/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/trace/src/Commons/BinaryTreeNodeAndTreeValue.php b/seed/php-model/trace/src/Commons/BinaryTreeNodeAndTreeValue.php index b9df04148fe..535fe49d959 100644 --- a/seed/php-model/trace/src/Commons/BinaryTreeNodeAndTreeValue.php +++ b/seed/php-model/trace/src/Commons/BinaryTreeNodeAndTreeValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BinaryTreeNodeAndTreeValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/BinaryTreeNodeValue.php b/seed/php-model/trace/src/Commons/BinaryTreeNodeValue.php index 107736efcdf..065f6a69c61 100644 --- a/seed/php-model/trace/src/Commons/BinaryTreeNodeValue.php +++ b/seed/php-model/trace/src/Commons/BinaryTreeNodeValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BinaryTreeNodeValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/BinaryTreeValue.php b/seed/php-model/trace/src/Commons/BinaryTreeValue.php index 68233339420..23b8c8a99d0 100644 --- a/seed/php-model/trace/src/Commons/BinaryTreeValue.php +++ b/seed/php-model/trace/src/Commons/BinaryTreeValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class BinaryTreeValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/DebugKeyValuePairs.php b/seed/php-model/trace/src/Commons/DebugKeyValuePairs.php index 1b49ffd8017..b9eac48897f 100644 --- a/seed/php-model/trace/src/Commons/DebugKeyValuePairs.php +++ b/seed/php-model/trace/src/Commons/DebugKeyValuePairs.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DebugKeyValuePairs extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/DebugMapValue.php b/seed/php-model/trace/src/Commons/DebugMapValue.php index 9ad22ade842..b441a7b6425 100644 --- a/seed/php-model/trace/src/Commons/DebugMapValue.php +++ b/seed/php-model/trace/src/Commons/DebugMapValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DebugMapValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/DoublyLinkedListNodeAndListValue.php b/seed/php-model/trace/src/Commons/DoublyLinkedListNodeAndListValue.php index 7f091236b8d..e44a82d5543 100644 --- a/seed/php-model/trace/src/Commons/DoublyLinkedListNodeAndListValue.php +++ b/seed/php-model/trace/src/Commons/DoublyLinkedListNodeAndListValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DoublyLinkedListNodeAndListValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/DoublyLinkedListNodeValue.php b/seed/php-model/trace/src/Commons/DoublyLinkedListNodeValue.php index ac7c03556a7..5d1993edce9 100644 --- a/seed/php-model/trace/src/Commons/DoublyLinkedListNodeValue.php +++ b/seed/php-model/trace/src/Commons/DoublyLinkedListNodeValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DoublyLinkedListNodeValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/DoublyLinkedListValue.php b/seed/php-model/trace/src/Commons/DoublyLinkedListValue.php index e06f43dd50a..0ddf039ec71 100644 --- a/seed/php-model/trace/src/Commons/DoublyLinkedListValue.php +++ b/seed/php-model/trace/src/Commons/DoublyLinkedListValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DoublyLinkedListValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/FileInfo.php b/seed/php-model/trace/src/Commons/FileInfo.php index 5ad37344dc2..cef90898d4f 100644 --- a/seed/php-model/trace/src/Commons/FileInfo.php +++ b/seed/php-model/trace/src/Commons/FileInfo.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FileInfo extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/GenericValue.php b/seed/php-model/trace/src/Commons/GenericValue.php index 1ee0b5f6d24..c6395300d5c 100644 --- a/seed/php-model/trace/src/Commons/GenericValue.php +++ b/seed/php-model/trace/src/Commons/GenericValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GenericValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/KeyValuePair.php b/seed/php-model/trace/src/Commons/KeyValuePair.php index fe26ea99ad5..f2ee4a86b54 100644 --- a/seed/php-model/trace/src/Commons/KeyValuePair.php +++ b/seed/php-model/trace/src/Commons/KeyValuePair.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class KeyValuePair extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/ListType.php b/seed/php-model/trace/src/Commons/ListType.php index 3b7a13398f2..d861dd3bdc5 100644 --- a/seed/php-model/trace/src/Commons/ListType.php +++ b/seed/php-model/trace/src/Commons/ListType.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ListType extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/MapType.php b/seed/php-model/trace/src/Commons/MapType.php index db306f01b93..b17f90896d4 100644 --- a/seed/php-model/trace/src/Commons/MapType.php +++ b/seed/php-model/trace/src/Commons/MapType.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class MapType extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/MapValue.php b/seed/php-model/trace/src/Commons/MapValue.php index fc03c54f6da..420c901ec0a 100644 --- a/seed/php-model/trace/src/Commons/MapValue.php +++ b/seed/php-model/trace/src/Commons/MapValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class MapValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/SinglyLinkedListNodeAndListValue.php b/seed/php-model/trace/src/Commons/SinglyLinkedListNodeAndListValue.php index dbf6b3a79fc..c36589ea37b 100644 --- a/seed/php-model/trace/src/Commons/SinglyLinkedListNodeAndListValue.php +++ b/seed/php-model/trace/src/Commons/SinglyLinkedListNodeAndListValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SinglyLinkedListNodeAndListValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/SinglyLinkedListNodeValue.php b/seed/php-model/trace/src/Commons/SinglyLinkedListNodeValue.php index d08e6103933..cfe1a9c560d 100644 --- a/seed/php-model/trace/src/Commons/SinglyLinkedListNodeValue.php +++ b/seed/php-model/trace/src/Commons/SinglyLinkedListNodeValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SinglyLinkedListNodeValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/SinglyLinkedListValue.php b/seed/php-model/trace/src/Commons/SinglyLinkedListValue.php index fce470b48b2..0312b9ea6c3 100644 --- a/seed/php-model/trace/src/Commons/SinglyLinkedListValue.php +++ b/seed/php-model/trace/src/Commons/SinglyLinkedListValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class SinglyLinkedListValue extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/TestCase.php b/seed/php-model/trace/src/Commons/TestCase.php index db7706b9a1f..b56cd355bd2 100644 --- a/seed/php-model/trace/src/Commons/TestCase.php +++ b/seed/php-model/trace/src/Commons/TestCase.php @@ -2,9 +2,9 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCase extends SerializableType { diff --git a/seed/php-model/trace/src/Commons/TestCaseWithExpectedResult.php b/seed/php-model/trace/src/Commons/TestCaseWithExpectedResult.php index 4787f1a8012..dec3e260190 100644 --- a/seed/php-model/trace/src/Commons/TestCaseWithExpectedResult.php +++ b/seed/php-model/trace/src/Commons/TestCaseWithExpectedResult.php @@ -2,8 +2,8 @@ namespace Seed\Commons; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseWithExpectedResult extends SerializableType { diff --git a/seed/php-model/trace/src/Core/ArrayType.php b/seed/php-model/trace/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/trace/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/trace/src/Core/Constant.php b/seed/php-model/trace/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/trace/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/trace/src/Core/Json/JsonDeserializer.php b/seed/php-model/trace/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/trace/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/trace/src/Core/Json/JsonEncoder.php b/seed/php-model/trace/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/trace/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/trace/src/Core/Json/SerializableType.php b/seed/php-model/trace/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/trace/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/trace/src/Core/Json/Utils.php b/seed/php-model/trace/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/trace/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/trace/src/Core/JsonDecoder.php b/seed/php-model/trace/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/trace/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/trace/src/Core/JsonDeserializer.php b/seed/php-model/trace/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/trace/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/trace/src/Core/JsonEncoder.php b/seed/php-model/trace/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/trace/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/trace/src/Core/SerializableType.php b/seed/php-model/trace/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/trace/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/trace/src/Core/Types/ArrayType.php b/seed/php-model/trace/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/trace/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/trace/src/Core/Types/Constant.php b/seed/php-model/trace/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/trace/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/trace/src/Core/Union.php b/seed/php-model/trace/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/trace/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/trace/src/Core/Utils.php b/seed/php-model/trace/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/trace/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/trace/src/LangServer/LangServerRequest.php b/seed/php-model/trace/src/LangServer/LangServerRequest.php index dac3d40c0dc..e0a21a139c5 100644 --- a/seed/php-model/trace/src/LangServer/LangServerRequest.php +++ b/seed/php-model/trace/src/LangServer/LangServerRequest.php @@ -2,8 +2,8 @@ namespace Seed\LangServer; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class LangServerRequest extends SerializableType { diff --git a/seed/php-model/trace/src/LangServer/LangServerResponse.php b/seed/php-model/trace/src/LangServer/LangServerResponse.php index 7f82c33cc4d..4b54f120620 100644 --- a/seed/php-model/trace/src/LangServer/LangServerResponse.php +++ b/seed/php-model/trace/src/LangServer/LangServerResponse.php @@ -2,8 +2,8 @@ namespace Seed\LangServer; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class LangServerResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Migration/Migration.php b/seed/php-model/trace/src/Migration/Migration.php index 5c8bf47cf42..24c4e77fc5a 100644 --- a/seed/php-model/trace/src/Migration/Migration.php +++ b/seed/php-model/trace/src/Migration/Migration.php @@ -2,8 +2,8 @@ namespace Seed\Migration; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Migration extends SerializableType { diff --git a/seed/php-model/trace/src/Playlist/Playlist.php b/seed/php-model/trace/src/Playlist/Playlist.php index 05cc93a3298..b5d0680e3c9 100644 --- a/seed/php-model/trace/src/Playlist/Playlist.php +++ b/seed/php-model/trace/src/Playlist/Playlist.php @@ -2,8 +2,8 @@ namespace Seed\Playlist; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Playlist extends SerializableType { diff --git a/seed/php-model/trace/src/Playlist/PlaylistCreateRequest.php b/seed/php-model/trace/src/Playlist/PlaylistCreateRequest.php index 355217356ef..fc746d86336 100644 --- a/seed/php-model/trace/src/Playlist/PlaylistCreateRequest.php +++ b/seed/php-model/trace/src/Playlist/PlaylistCreateRequest.php @@ -2,9 +2,9 @@ namespace Seed\Playlist; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class PlaylistCreateRequest extends SerializableType { diff --git a/seed/php-model/trace/src/Playlist/UpdatePlaylistRequest.php b/seed/php-model/trace/src/Playlist/UpdatePlaylistRequest.php index ec09723b666..1d868665bea 100644 --- a/seed/php-model/trace/src/Playlist/UpdatePlaylistRequest.php +++ b/seed/php-model/trace/src/Playlist/UpdatePlaylistRequest.php @@ -2,9 +2,9 @@ namespace Seed\Playlist; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UpdatePlaylistRequest extends SerializableType { diff --git a/seed/php-model/trace/src/Problem/CreateProblemRequest.php b/seed/php-model/trace/src/Problem/CreateProblemRequest.php index 187e555c335..bb0a1722aec 100644 --- a/seed/php-model/trace/src/Problem/CreateProblemRequest.php +++ b/seed/php-model/trace/src/Problem/CreateProblemRequest.php @@ -2,10 +2,10 @@ namespace Seed\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Commons\TestCaseWithExpectedResult; class CreateProblemRequest extends SerializableType diff --git a/seed/php-model/trace/src/Problem/GenericCreateProblemError.php b/seed/php-model/trace/src/Problem/GenericCreateProblemError.php index 4546cd71a66..a9c6bf52d7e 100644 --- a/seed/php-model/trace/src/Problem/GenericCreateProblemError.php +++ b/seed/php-model/trace/src/Problem/GenericCreateProblemError.php @@ -2,8 +2,8 @@ namespace Seed\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GenericCreateProblemError extends SerializableType { diff --git a/seed/php-model/trace/src/Problem/GetDefaultStarterFilesResponse.php b/seed/php-model/trace/src/Problem/GetDefaultStarterFilesResponse.php index 432b49260bb..a3a16b8fbbe 100644 --- a/seed/php-model/trace/src/Problem/GetDefaultStarterFilesResponse.php +++ b/seed/php-model/trace/src/Problem/GetDefaultStarterFilesResponse.php @@ -2,10 +2,10 @@ namespace Seed\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetDefaultStarterFilesResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Problem/ProblemDescription.php b/seed/php-model/trace/src/Problem/ProblemDescription.php index 1ec68e053a9..1fc6b2e8612 100644 --- a/seed/php-model/trace/src/Problem/ProblemDescription.php +++ b/seed/php-model/trace/src/Problem/ProblemDescription.php @@ -2,9 +2,9 @@ namespace Seed\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ProblemDescription extends SerializableType { diff --git a/seed/php-model/trace/src/Problem/ProblemFiles.php b/seed/php-model/trace/src/Problem/ProblemFiles.php index dbe0ce59c8e..fcfa8fcb057 100644 --- a/seed/php-model/trace/src/Problem/ProblemFiles.php +++ b/seed/php-model/trace/src/Problem/ProblemFiles.php @@ -2,10 +2,10 @@ namespace Seed\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\FileInfo; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ProblemFiles extends SerializableType { diff --git a/seed/php-model/trace/src/Problem/ProblemInfo.php b/seed/php-model/trace/src/Problem/ProblemInfo.php index 2d963f0e7d7..4f1871d757d 100644 --- a/seed/php-model/trace/src/Problem/ProblemInfo.php +++ b/seed/php-model/trace/src/Problem/ProblemInfo.php @@ -2,10 +2,10 @@ namespace Seed\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Commons\TestCaseWithExpectedResult; class ProblemInfo extends SerializableType diff --git a/seed/php-model/trace/src/Problem/UpdateProblemResponse.php b/seed/php-model/trace/src/Problem/UpdateProblemResponse.php index 57e8432a56e..508cfd8677d 100644 --- a/seed/php-model/trace/src/Problem/UpdateProblemResponse.php +++ b/seed/php-model/trace/src/Problem/UpdateProblemResponse.php @@ -2,8 +2,8 @@ namespace Seed\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UpdateProblemResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Problem/VariableTypeAndName.php b/seed/php-model/trace/src/Problem/VariableTypeAndName.php index ef82e26d6fe..8b834fd2cc3 100644 --- a/seed/php-model/trace/src/Problem/VariableTypeAndName.php +++ b/seed/php-model/trace/src/Problem/VariableTypeAndName.php @@ -2,8 +2,8 @@ namespace Seed\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class VariableTypeAndName extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/BuildingExecutorResponse.php b/seed/php-model/trace/src/Submission/BuildingExecutorResponse.php index 5db9de5fc74..ab3aa54859f 100644 --- a/seed/php-model/trace/src/Submission/BuildingExecutorResponse.php +++ b/seed/php-model/trace/src/Submission/BuildingExecutorResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BuildingExecutorResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/CompileError.php b/seed/php-model/trace/src/Submission/CompileError.php index deeba3d8264..7172a40f4d4 100644 --- a/seed/php-model/trace/src/Submission/CompileError.php +++ b/seed/php-model/trace/src/Submission/CompileError.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CompileError extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/CustomTestCasesUnsupported.php b/seed/php-model/trace/src/Submission/CustomTestCasesUnsupported.php index 6fcaf9668e8..12f74380686 100644 --- a/seed/php-model/trace/src/Submission/CustomTestCasesUnsupported.php +++ b/seed/php-model/trace/src/Submission/CustomTestCasesUnsupported.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CustomTestCasesUnsupported extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/ErroredResponse.php b/seed/php-model/trace/src/Submission/ErroredResponse.php index efe9aff2089..9a75a4c744d 100644 --- a/seed/php-model/trace/src/Submission/ErroredResponse.php +++ b/seed/php-model/trace/src/Submission/ErroredResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ErroredResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/ExceptionInfo.php b/seed/php-model/trace/src/Submission/ExceptionInfo.php index 2a45de25ee0..9bc0c673445 100644 --- a/seed/php-model/trace/src/Submission/ExceptionInfo.php +++ b/seed/php-model/trace/src/Submission/ExceptionInfo.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExceptionInfo extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/ExecutionSessionResponse.php b/seed/php-model/trace/src/Submission/ExecutionSessionResponse.php index d2cc677a866..5717dbef5dd 100644 --- a/seed/php-model/trace/src/Submission/ExecutionSessionResponse.php +++ b/seed/php-model/trace/src/Submission/ExecutionSessionResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Language; class ExecutionSessionResponse extends SerializableType diff --git a/seed/php-model/trace/src/Submission/ExecutionSessionState.php b/seed/php-model/trace/src/Submission/ExecutionSessionState.php index cef58649486..7e9712ff450 100644 --- a/seed/php-model/trace/src/Submission/ExecutionSessionState.php +++ b/seed/php-model/trace/src/Submission/ExecutionSessionState.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Language; class ExecutionSessionState extends SerializableType diff --git a/seed/php-model/trace/src/Submission/ExistingSubmissionExecuting.php b/seed/php-model/trace/src/Submission/ExistingSubmissionExecuting.php index b77283521b8..f25f0d088a6 100644 --- a/seed/php-model/trace/src/Submission/ExistingSubmissionExecuting.php +++ b/seed/php-model/trace/src/Submission/ExistingSubmissionExecuting.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExistingSubmissionExecuting extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/ExpressionLocation.php b/seed/php-model/trace/src/Submission/ExpressionLocation.php index c0bea84ab87..6e081e3a052 100644 --- a/seed/php-model/trace/src/Submission/ExpressionLocation.php +++ b/seed/php-model/trace/src/Submission/ExpressionLocation.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExpressionLocation extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/FinishedResponse.php b/seed/php-model/trace/src/Submission/FinishedResponse.php index 9d57b2014ce..f67db0c18ec 100644 --- a/seed/php-model/trace/src/Submission/FinishedResponse.php +++ b/seed/php-model/trace/src/Submission/FinishedResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FinishedResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/GetExecutionSessionStateResponse.php b/seed/php-model/trace/src/Submission/GetExecutionSessionStateResponse.php index edf5f00f490..fe63684e929 100644 --- a/seed/php-model/trace/src/Submission/GetExecutionSessionStateResponse.php +++ b/seed/php-model/trace/src/Submission/GetExecutionSessionStateResponse.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetExecutionSessionStateResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/GetSubmissionStateResponse.php b/seed/php-model/trace/src/Submission/GetSubmissionStateResponse.php index 14ddbe97e97..8adc8fc472a 100644 --- a/seed/php-model/trace/src/Submission/GetSubmissionStateResponse.php +++ b/seed/php-model/trace/src/Submission/GetSubmissionStateResponse.php @@ -2,10 +2,10 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; -use Seed\Core\JsonProperty; -use Seed\Core\DateType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\DateType; use Seed\Commons\Language; class GetSubmissionStateResponse extends SerializableType diff --git a/seed/php-model/trace/src/Submission/GetTraceResponsesPageRequest.php b/seed/php-model/trace/src/Submission/GetTraceResponsesPageRequest.php index be43b4dcd4f..a8b9312f8e2 100644 --- a/seed/php-model/trace/src/Submission/GetTraceResponsesPageRequest.php +++ b/seed/php-model/trace/src/Submission/GetTraceResponsesPageRequest.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetTraceResponsesPageRequest extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/GradedResponse.php b/seed/php-model/trace/src/Submission/GradedResponse.php index da605b1cee2..d68943c6966 100644 --- a/seed/php-model/trace/src/Submission/GradedResponse.php +++ b/seed/php-model/trace/src/Submission/GradedResponse.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GradedResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/GradedResponseV2.php b/seed/php-model/trace/src/Submission/GradedResponseV2.php index 5eaa6df82e4..c8e0f707e95 100644 --- a/seed/php-model/trace/src/Submission/GradedResponseV2.php +++ b/seed/php-model/trace/src/Submission/GradedResponseV2.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GradedResponseV2 extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/GradedTestCaseUpdate.php b/seed/php-model/trace/src/Submission/GradedTestCaseUpdate.php index 6bbd74fc34f..10613ae0f81 100644 --- a/seed/php-model/trace/src/Submission/GradedTestCaseUpdate.php +++ b/seed/php-model/trace/src/Submission/GradedTestCaseUpdate.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GradedTestCaseUpdate extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/InitializeProblemRequest.php b/seed/php-model/trace/src/Submission/InitializeProblemRequest.php index 23331d36890..e9cd0987b36 100644 --- a/seed/php-model/trace/src/Submission/InitializeProblemRequest.php +++ b/seed/php-model/trace/src/Submission/InitializeProblemRequest.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class InitializeProblemRequest extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/InternalError.php b/seed/php-model/trace/src/Submission/InternalError.php index 63036a22400..61c54c3d157 100644 --- a/seed/php-model/trace/src/Submission/InternalError.php +++ b/seed/php-model/trace/src/Submission/InternalError.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class InternalError extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/InvalidRequestResponse.php b/seed/php-model/trace/src/Submission/InvalidRequestResponse.php index 87fac4dd59b..87c9ffb72ec 100644 --- a/seed/php-model/trace/src/Submission/InvalidRequestResponse.php +++ b/seed/php-model/trace/src/Submission/InvalidRequestResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class InvalidRequestResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/LightweightStackframeInformation.php b/seed/php-model/trace/src/Submission/LightweightStackframeInformation.php index 12e63d6d95c..715b1683f18 100644 --- a/seed/php-model/trace/src/Submission/LightweightStackframeInformation.php +++ b/seed/php-model/trace/src/Submission/LightweightStackframeInformation.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class LightweightStackframeInformation extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/RecordedResponseNotification.php b/seed/php-model/trace/src/Submission/RecordedResponseNotification.php index 10b67aed282..df014518da3 100644 --- a/seed/php-model/trace/src/Submission/RecordedResponseNotification.php +++ b/seed/php-model/trace/src/Submission/RecordedResponseNotification.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RecordedResponseNotification extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/RecordedTestCaseUpdate.php b/seed/php-model/trace/src/Submission/RecordedTestCaseUpdate.php index 7f0553c81d2..2188cd3c4dc 100644 --- a/seed/php-model/trace/src/Submission/RecordedTestCaseUpdate.php +++ b/seed/php-model/trace/src/Submission/RecordedTestCaseUpdate.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RecordedTestCaseUpdate extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/RecordingResponseNotification.php b/seed/php-model/trace/src/Submission/RecordingResponseNotification.php index 121e4e91b85..21bda9f013c 100644 --- a/seed/php-model/trace/src/Submission/RecordingResponseNotification.php +++ b/seed/php-model/trace/src/Submission/RecordingResponseNotification.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RecordingResponseNotification extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/RunningResponse.php b/seed/php-model/trace/src/Submission/RunningResponse.php index f87f9c1953c..9c027136322 100644 --- a/seed/php-model/trace/src/Submission/RunningResponse.php +++ b/seed/php-model/trace/src/Submission/RunningResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RunningResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/RuntimeError.php b/seed/php-model/trace/src/Submission/RuntimeError.php index 3626c4f02bc..db9ccfee05c 100644 --- a/seed/php-model/trace/src/Submission/RuntimeError.php +++ b/seed/php-model/trace/src/Submission/RuntimeError.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RuntimeError extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/Scope.php b/seed/php-model/trace/src/Submission/Scope.php index 743ce53fa97..ad791c2215b 100644 --- a/seed/php-model/trace/src/Submission/Scope.php +++ b/seed/php-model/trace/src/Submission/Scope.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Scope extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/StackFrame.php b/seed/php-model/trace/src/Submission/StackFrame.php index a017cdfdad7..1a3350137d5 100644 --- a/seed/php-model/trace/src/Submission/StackFrame.php +++ b/seed/php-model/trace/src/Submission/StackFrame.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class StackFrame extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/StackInformation.php b/seed/php-model/trace/src/Submission/StackInformation.php index a19bf79e36e..0cdf9665ae5 100644 --- a/seed/php-model/trace/src/Submission/StackInformation.php +++ b/seed/php-model/trace/src/Submission/StackInformation.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StackInformation extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/StderrResponse.php b/seed/php-model/trace/src/Submission/StderrResponse.php index e0b5860f7fe..731509ced3f 100644 --- a/seed/php-model/trace/src/Submission/StderrResponse.php +++ b/seed/php-model/trace/src/Submission/StderrResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StderrResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/StdoutResponse.php b/seed/php-model/trace/src/Submission/StdoutResponse.php index 0307bea0159..e26576e12fd 100644 --- a/seed/php-model/trace/src/Submission/StdoutResponse.php +++ b/seed/php-model/trace/src/Submission/StdoutResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StdoutResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/StopRequest.php b/seed/php-model/trace/src/Submission/StopRequest.php index 94d4f0854f5..b4d782e1d4b 100644 --- a/seed/php-model/trace/src/Submission/StopRequest.php +++ b/seed/php-model/trace/src/Submission/StopRequest.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StopRequest extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/StoppedResponse.php b/seed/php-model/trace/src/Submission/StoppedResponse.php index b6f13bf0c7e..b8917b2465b 100644 --- a/seed/php-model/trace/src/Submission/StoppedResponse.php +++ b/seed/php-model/trace/src/Submission/StoppedResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StoppedResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/SubmissionFileInfo.php b/seed/php-model/trace/src/Submission/SubmissionFileInfo.php index fc6e53bbe3b..69672f02108 100644 --- a/seed/php-model/trace/src/Submission/SubmissionFileInfo.php +++ b/seed/php-model/trace/src/Submission/SubmissionFileInfo.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SubmissionFileInfo extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/SubmissionIdNotFound.php b/seed/php-model/trace/src/Submission/SubmissionIdNotFound.php index 336ea2d77aa..c7b269a9021 100644 --- a/seed/php-model/trace/src/Submission/SubmissionIdNotFound.php +++ b/seed/php-model/trace/src/Submission/SubmissionIdNotFound.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SubmissionIdNotFound extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/SubmitRequestV2.php b/seed/php-model/trace/src/Submission/SubmitRequestV2.php index 32164ec1e00..dff33895673 100644 --- a/seed/php-model/trace/src/Submission/SubmitRequestV2.php +++ b/seed/php-model/trace/src/Submission/SubmitRequestV2.php @@ -2,10 +2,10 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class SubmitRequestV2 extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TerminatedResponse.php b/seed/php-model/trace/src/Submission/TerminatedResponse.php index 081de4748d5..6be4ba8bcdb 100644 --- a/seed/php-model/trace/src/Submission/TerminatedResponse.php +++ b/seed/php-model/trace/src/Submission/TerminatedResponse.php @@ -2,7 +2,7 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class TerminatedResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TestCaseHiddenGrade.php b/seed/php-model/trace/src/Submission/TestCaseHiddenGrade.php index ed09f0ef734..e77946ada92 100644 --- a/seed/php-model/trace/src/Submission/TestCaseHiddenGrade.php +++ b/seed/php-model/trace/src/Submission/TestCaseHiddenGrade.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseHiddenGrade extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TestCaseNonHiddenGrade.php b/seed/php-model/trace/src/Submission/TestCaseNonHiddenGrade.php index b75990f17f7..5f516b2184a 100644 --- a/seed/php-model/trace/src/Submission/TestCaseNonHiddenGrade.php +++ b/seed/php-model/trace/src/Submission/TestCaseNonHiddenGrade.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseNonHiddenGrade extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TestCaseResult.php b/seed/php-model/trace/src/Submission/TestCaseResult.php index c13aa4fa0a6..133d618a83c 100644 --- a/seed/php-model/trace/src/Submission/TestCaseResult.php +++ b/seed/php-model/trace/src/Submission/TestCaseResult.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseResult extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TestCaseResultWithStdout.php b/seed/php-model/trace/src/Submission/TestCaseResultWithStdout.php index 122260ab055..82ea6b42158 100644 --- a/seed/php-model/trace/src/Submission/TestCaseResultWithStdout.php +++ b/seed/php-model/trace/src/Submission/TestCaseResultWithStdout.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseResultWithStdout extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TestSubmissionState.php b/seed/php-model/trace/src/Submission/TestSubmissionState.php index 23894afaff9..7a38ddc8df8 100644 --- a/seed/php-model/trace/src/Submission/TestSubmissionState.php +++ b/seed/php-model/trace/src/Submission/TestSubmissionState.php @@ -2,10 +2,10 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\TestCase; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class TestSubmissionState extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TestSubmissionStatusV2.php b/seed/php-model/trace/src/Submission/TestSubmissionStatusV2.php index 21c40dd37aa..202b7c436bd 100644 --- a/seed/php-model/trace/src/Submission/TestSubmissionStatusV2.php +++ b/seed/php-model/trace/src/Submission/TestSubmissionStatusV2.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; use Seed\V2\Problem\ProblemInfoV2; class TestSubmissionStatusV2 extends SerializableType diff --git a/seed/php-model/trace/src/Submission/TestSubmissionUpdate.php b/seed/php-model/trace/src/Submission/TestSubmissionUpdate.php index 499423cb26a..ae5b0caa64c 100644 --- a/seed/php-model/trace/src/Submission/TestSubmissionUpdate.php +++ b/seed/php-model/trace/src/Submission/TestSubmissionUpdate.php @@ -2,10 +2,10 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; -use Seed\Core\JsonProperty; -use Seed\Core\DateType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\DateType; class TestSubmissionUpdate extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TraceResponse.php b/seed/php-model/trace/src/Submission/TraceResponse.php index 07c4bd0b3ef..dd2d0936f88 100644 --- a/seed/php-model/trace/src/Submission/TraceResponse.php +++ b/seed/php-model/trace/src/Submission/TraceResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TraceResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TraceResponseV2.php b/seed/php-model/trace/src/Submission/TraceResponseV2.php index 75b56dd199d..387430f99ff 100644 --- a/seed/php-model/trace/src/Submission/TraceResponseV2.php +++ b/seed/php-model/trace/src/Submission/TraceResponseV2.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TraceResponseV2 extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TraceResponsesPage.php b/seed/php-model/trace/src/Submission/TraceResponsesPage.php index 12b1bec06ae..4578b7d3f62 100644 --- a/seed/php-model/trace/src/Submission/TraceResponsesPage.php +++ b/seed/php-model/trace/src/Submission/TraceResponsesPage.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TraceResponsesPage extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TraceResponsesPageV2.php b/seed/php-model/trace/src/Submission/TraceResponsesPageV2.php index f49b6e86e4b..57d1c04a90f 100644 --- a/seed/php-model/trace/src/Submission/TraceResponsesPageV2.php +++ b/seed/php-model/trace/src/Submission/TraceResponsesPageV2.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TraceResponsesPageV2 extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TracedFile.php b/seed/php-model/trace/src/Submission/TracedFile.php index 129c534c69d..db04ec4939c 100644 --- a/seed/php-model/trace/src/Submission/TracedFile.php +++ b/seed/php-model/trace/src/Submission/TracedFile.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TracedFile extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/TracedTestCase.php b/seed/php-model/trace/src/Submission/TracedTestCase.php index 2fc2c088783..ef196eddbc1 100644 --- a/seed/php-model/trace/src/Submission/TracedTestCase.php +++ b/seed/php-model/trace/src/Submission/TracedTestCase.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TracedTestCase extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/UnexpectedLanguageError.php b/seed/php-model/trace/src/Submission/UnexpectedLanguageError.php index 676946bae2c..5106c2b99a3 100644 --- a/seed/php-model/trace/src/Submission/UnexpectedLanguageError.php +++ b/seed/php-model/trace/src/Submission/UnexpectedLanguageError.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class UnexpectedLanguageError extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceFiles.php b/seed/php-model/trace/src/Submission/WorkspaceFiles.php index 369ceaec605..957f5d95bab 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceFiles.php +++ b/seed/php-model/trace/src/Submission/WorkspaceFiles.php @@ -2,10 +2,10 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\FileInfo; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WorkspaceFiles extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceRanResponse.php b/seed/php-model/trace/src/Submission/WorkspaceRanResponse.php index e74a5847d5b..838c10f2083 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceRanResponse.php +++ b/seed/php-model/trace/src/Submission/WorkspaceRanResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WorkspaceRanResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceRunDetails.php b/seed/php-model/trace/src/Submission/WorkspaceRunDetails.php index 43e4c3c9f91..20f327abd0c 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceRunDetails.php +++ b/seed/php-model/trace/src/Submission/WorkspaceRunDetails.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WorkspaceRunDetails extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceStarterFilesResponse.php b/seed/php-model/trace/src/Submission/WorkspaceStarterFilesResponse.php index bbbde1db956..74d66ab8ed6 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceStarterFilesResponse.php +++ b/seed/php-model/trace/src/Submission/WorkspaceStarterFilesResponse.php @@ -2,10 +2,10 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WorkspaceStarterFilesResponse extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceStarterFilesResponseV2.php b/seed/php-model/trace/src/Submission/WorkspaceStarterFilesResponseV2.php index 5017648c563..a8aadcb5245 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceStarterFilesResponseV2.php +++ b/seed/php-model/trace/src/Submission/WorkspaceStarterFilesResponseV2.php @@ -2,11 +2,11 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; use Seed\V2\Problem\Files; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WorkspaceStarterFilesResponseV2 extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceSubmissionState.php b/seed/php-model/trace/src/Submission/WorkspaceSubmissionState.php index 4dde4b17791..e29e1e2bdd6 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceSubmissionState.php +++ b/seed/php-model/trace/src/Submission/WorkspaceSubmissionState.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WorkspaceSubmissionState extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceSubmissionStatusV2.php b/seed/php-model/trace/src/Submission/WorkspaceSubmissionStatusV2.php index f95936f6c62..e57352564a6 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceSubmissionStatusV2.php +++ b/seed/php-model/trace/src/Submission/WorkspaceSubmissionStatusV2.php @@ -2,9 +2,9 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WorkspaceSubmissionStatusV2 extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceSubmissionUpdate.php b/seed/php-model/trace/src/Submission/WorkspaceSubmissionUpdate.php index 298cf3343b2..7a9dff564a4 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceSubmissionUpdate.php +++ b/seed/php-model/trace/src/Submission/WorkspaceSubmissionUpdate.php @@ -2,10 +2,10 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; -use Seed\Core\JsonProperty; -use Seed\Core\DateType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\DateType; class WorkspaceSubmissionUpdate extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceSubmitRequest.php b/seed/php-model/trace/src/Submission/WorkspaceSubmitRequest.php index 40735b912db..b28562222bb 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceSubmitRequest.php +++ b/seed/php-model/trace/src/Submission/WorkspaceSubmitRequest.php @@ -2,10 +2,10 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class WorkspaceSubmitRequest extends SerializableType { diff --git a/seed/php-model/trace/src/Submission/WorkspaceTracedUpdate.php b/seed/php-model/trace/src/Submission/WorkspaceTracedUpdate.php index 239022034ae..0a76f3e2f7a 100644 --- a/seed/php-model/trace/src/Submission/WorkspaceTracedUpdate.php +++ b/seed/php-model/trace/src/Submission/WorkspaceTracedUpdate.php @@ -2,8 +2,8 @@ namespace Seed\Submission; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WorkspaceTracedUpdate extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/BasicCustomFiles.php b/seed/php-model/trace/src/V2/Problem/BasicCustomFiles.php index 5aff473c378..9661bf7458d 100644 --- a/seed/php-model/trace/src/V2/Problem/BasicCustomFiles.php +++ b/seed/php-model/trace/src/V2/Problem/BasicCustomFiles.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class BasicCustomFiles extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/BasicTestCaseTemplate.php b/seed/php-model/trace/src/V2/Problem/BasicTestCaseTemplate.php index 9ae6dee5126..a886775b177 100644 --- a/seed/php-model/trace/src/V2/Problem/BasicTestCaseTemplate.php +++ b/seed/php-model/trace/src/V2/Problem/BasicTestCaseTemplate.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BasicTestCaseTemplate extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/CreateProblemRequestV2.php b/seed/php-model/trace/src/V2/Problem/CreateProblemRequestV2.php index e1bbad33383..57fe530c2bd 100644 --- a/seed/php-model/trace/src/V2/Problem/CreateProblemRequestV2.php +++ b/seed/php-model/trace/src/V2/Problem/CreateProblemRequestV2.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Problem\ProblemDescription; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Commons\Language; class CreateProblemRequestV2 extends SerializableType diff --git a/seed/php-model/trace/src/V2/Problem/DeepEqualityCorrectnessCheck.php b/seed/php-model/trace/src/V2/Problem/DeepEqualityCorrectnessCheck.php index 9f0b44d8b1d..739d69ea67f 100644 --- a/seed/php-model/trace/src/V2/Problem/DeepEqualityCorrectnessCheck.php +++ b/seed/php-model/trace/src/V2/Problem/DeepEqualityCorrectnessCheck.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DeepEqualityCorrectnessCheck extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/DefaultProvidedFile.php b/seed/php-model/trace/src/V2/Problem/DefaultProvidedFile.php index 7b8eb930e29..72c670c0edb 100644 --- a/seed/php-model/trace/src/V2/Problem/DefaultProvidedFile.php +++ b/seed/php-model/trace/src/V2/Problem/DefaultProvidedFile.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DefaultProvidedFile extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/FileInfoV2.php b/seed/php-model/trace/src/V2/Problem/FileInfoV2.php index 14a58686892..a68392f476c 100644 --- a/seed/php-model/trace/src/V2/Problem/FileInfoV2.php +++ b/seed/php-model/trace/src/V2/Problem/FileInfoV2.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FileInfoV2 extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/Files.php b/seed/php-model/trace/src/V2/Problem/Files.php index 0c5b31f5758..2015a8b458a 100644 --- a/seed/php-model/trace/src/V2/Problem/Files.php +++ b/seed/php-model/trace/src/V2/Problem/Files.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Files extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/FunctionImplementation.php b/seed/php-model/trace/src/V2/Problem/FunctionImplementation.php index c315a5143d7..2dac5f5a52f 100644 --- a/seed/php-model/trace/src/V2/Problem/FunctionImplementation.php +++ b/seed/php-model/trace/src/V2/Problem/FunctionImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FunctionImplementation extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/FunctionImplementationForMultipleLanguages.php b/seed/php-model/trace/src/V2/Problem/FunctionImplementationForMultipleLanguages.php index dbe7ebb2ae3..2e1aa96b27e 100644 --- a/seed/php-model/trace/src/V2/Problem/FunctionImplementationForMultipleLanguages.php +++ b/seed/php-model/trace/src/V2/Problem/FunctionImplementationForMultipleLanguages.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class FunctionImplementationForMultipleLanguages extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/GeneratedFiles.php b/seed/php-model/trace/src/V2/Problem/GeneratedFiles.php index b8288568c8d..16805e31d6b 100644 --- a/seed/php-model/trace/src/V2/Problem/GeneratedFiles.php +++ b/seed/php-model/trace/src/V2/Problem/GeneratedFiles.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GeneratedFiles extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/GetBasicSolutionFileRequest.php b/seed/php-model/trace/src/V2/Problem/GetBasicSolutionFileRequest.php index b1564367fec..74c2e4be1f2 100644 --- a/seed/php-model/trace/src/V2/Problem/GetBasicSolutionFileRequest.php +++ b/seed/php-model/trace/src/V2/Problem/GetBasicSolutionFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetBasicSolutionFileRequest extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/GetBasicSolutionFileResponse.php b/seed/php-model/trace/src/V2/Problem/GetBasicSolutionFileResponse.php index 4366798e471..19280de59e0 100644 --- a/seed/php-model/trace/src/V2/Problem/GetBasicSolutionFileResponse.php +++ b/seed/php-model/trace/src/V2/Problem/GetBasicSolutionFileResponse.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetBasicSolutionFileResponse extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/GetFunctionSignatureRequest.php b/seed/php-model/trace/src/V2/Problem/GetFunctionSignatureRequest.php index 9764e687eea..fb30fefaaf4 100644 --- a/seed/php-model/trace/src/V2/Problem/GetFunctionSignatureRequest.php +++ b/seed/php-model/trace/src/V2/Problem/GetFunctionSignatureRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetFunctionSignatureRequest extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/GetFunctionSignatureResponse.php b/seed/php-model/trace/src/V2/Problem/GetFunctionSignatureResponse.php index 2b4d5c73dce..077f8f5643b 100644 --- a/seed/php-model/trace/src/V2/Problem/GetFunctionSignatureResponse.php +++ b/seed/php-model/trace/src/V2/Problem/GetFunctionSignatureResponse.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetFunctionSignatureResponse extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/GetGeneratedTestCaseFileRequest.php b/seed/php-model/trace/src/V2/Problem/GetGeneratedTestCaseFileRequest.php index 65f7ad96f5c..308ecd59658 100644 --- a/seed/php-model/trace/src/V2/Problem/GetGeneratedTestCaseFileRequest.php +++ b/seed/php-model/trace/src/V2/Problem/GetGeneratedTestCaseFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetGeneratedTestCaseFileRequest extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/GetGeneratedTestCaseTemplateFileRequest.php b/seed/php-model/trace/src/V2/Problem/GetGeneratedTestCaseTemplateFileRequest.php index 84adb5fec3d..33984e10649 100644 --- a/seed/php-model/trace/src/V2/Problem/GetGeneratedTestCaseTemplateFileRequest.php +++ b/seed/php-model/trace/src/V2/Problem/GetGeneratedTestCaseTemplateFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetGeneratedTestCaseTemplateFileRequest extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/LightweightProblemInfoV2.php b/seed/php-model/trace/src/V2/Problem/LightweightProblemInfoV2.php index a31c39034d9..617792b4264 100644 --- a/seed/php-model/trace/src/V2/Problem/LightweightProblemInfoV2.php +++ b/seed/php-model/trace/src/V2/Problem/LightweightProblemInfoV2.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class LightweightProblemInfoV2 extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/NonVoidFunctionDefinition.php b/seed/php-model/trace/src/V2/Problem/NonVoidFunctionDefinition.php index 99df1f7ea66..40f970ce36d 100644 --- a/seed/php-model/trace/src/V2/Problem/NonVoidFunctionDefinition.php +++ b/seed/php-model/trace/src/V2/Problem/NonVoidFunctionDefinition.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NonVoidFunctionDefinition extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/NonVoidFunctionSignature.php b/seed/php-model/trace/src/V2/Problem/NonVoidFunctionSignature.php index 4f8e6056b54..a533617aec7 100644 --- a/seed/php-model/trace/src/V2/Problem/NonVoidFunctionSignature.php +++ b/seed/php-model/trace/src/V2/Problem/NonVoidFunctionSignature.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class NonVoidFunctionSignature extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/Parameter.php b/seed/php-model/trace/src/V2/Problem/Parameter.php index 9ba3592cf6c..8a710343948 100644 --- a/seed/php-model/trace/src/V2/Problem/Parameter.php +++ b/seed/php-model/trace/src/V2/Problem/Parameter.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Parameter extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/ProblemInfoV2.php b/seed/php-model/trace/src/V2/Problem/ProblemInfoV2.php index 704a43e2bc3..62ad9b9a40f 100644 --- a/seed/php-model/trace/src/V2/Problem/ProblemInfoV2.php +++ b/seed/php-model/trace/src/V2/Problem/ProblemInfoV2.php @@ -2,11 +2,11 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Problem\ProblemDescription; use Seed\Commons\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class ProblemInfoV2 extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/TestCaseExpects.php b/seed/php-model/trace/src/V2/Problem/TestCaseExpects.php index 4f0030cf99a..de74cd61f3e 100644 --- a/seed/php-model/trace/src/V2/Problem/TestCaseExpects.php +++ b/seed/php-model/trace/src/V2/Problem/TestCaseExpects.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseExpects extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/TestCaseImplementation.php b/seed/php-model/trace/src/V2/Problem/TestCaseImplementation.php index 239f40fb9fc..fd85a300368 100644 --- a/seed/php-model/trace/src/V2/Problem/TestCaseImplementation.php +++ b/seed/php-model/trace/src/V2/Problem/TestCaseImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseImplementation extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/TestCaseImplementationDescription.php b/seed/php-model/trace/src/V2/Problem/TestCaseImplementationDescription.php index b077b591cd3..d48916f6014 100644 --- a/seed/php-model/trace/src/V2/Problem/TestCaseImplementationDescription.php +++ b/seed/php-model/trace/src/V2/Problem/TestCaseImplementationDescription.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCaseImplementationDescription extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/TestCaseMetadata.php b/seed/php-model/trace/src/V2/Problem/TestCaseMetadata.php index 0c4cbc331a9..70e96a88955 100644 --- a/seed/php-model/trace/src/V2/Problem/TestCaseMetadata.php +++ b/seed/php-model/trace/src/V2/Problem/TestCaseMetadata.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseMetadata extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/TestCaseTemplate.php b/seed/php-model/trace/src/V2/Problem/TestCaseTemplate.php index 333146e51ad..b39cbb27614 100644 --- a/seed/php-model/trace/src/V2/Problem/TestCaseTemplate.php +++ b/seed/php-model/trace/src/V2/Problem/TestCaseTemplate.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseTemplate extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/TestCaseV2.php b/seed/php-model/trace/src/V2/Problem/TestCaseV2.php index ae666b7592a..c6cd05587ca 100644 --- a/seed/php-model/trace/src/V2/Problem/TestCaseV2.php +++ b/seed/php-model/trace/src/V2/Problem/TestCaseV2.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCaseV2 extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/TestCaseWithActualResultImplementation.php b/seed/php-model/trace/src/V2/Problem/TestCaseWithActualResultImplementation.php index f376957da34..a054c4414cd 100644 --- a/seed/php-model/trace/src/V2/Problem/TestCaseWithActualResultImplementation.php +++ b/seed/php-model/trace/src/V2/Problem/TestCaseWithActualResultImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseWithActualResultImplementation extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/VoidFunctionDefinition.php b/seed/php-model/trace/src/V2/Problem/VoidFunctionDefinition.php index e6fd0ca7d89..68a4f2b1c87 100644 --- a/seed/php-model/trace/src/V2/Problem/VoidFunctionDefinition.php +++ b/seed/php-model/trace/src/V2/Problem/VoidFunctionDefinition.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionDefinition extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/VoidFunctionDefinitionThatTakesActualResult.php b/seed/php-model/trace/src/V2/Problem/VoidFunctionDefinitionThatTakesActualResult.php index 8586b95923c..5749fdc3045 100644 --- a/seed/php-model/trace/src/V2/Problem/VoidFunctionDefinitionThatTakesActualResult.php +++ b/seed/php-model/trace/src/V2/Problem/VoidFunctionDefinitionThatTakesActualResult.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; /** * The generated signature will include an additional param, actualResult diff --git a/seed/php-model/trace/src/V2/Problem/VoidFunctionSignature.php b/seed/php-model/trace/src/V2/Problem/VoidFunctionSignature.php index ac55788cbf1..9a87eb13aa0 100644 --- a/seed/php-model/trace/src/V2/Problem/VoidFunctionSignature.php +++ b/seed/php-model/trace/src/V2/Problem/VoidFunctionSignature.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionSignature extends SerializableType { diff --git a/seed/php-model/trace/src/V2/Problem/VoidFunctionSignatureThatTakesActualResult.php b/seed/php-model/trace/src/V2/Problem/VoidFunctionSignatureThatTakesActualResult.php index 3f3de22f17d..3101d9a444f 100644 --- a/seed/php-model/trace/src/V2/Problem/VoidFunctionSignatureThatTakesActualResult.php +++ b/seed/php-model/trace/src/V2/Problem/VoidFunctionSignatureThatTakesActualResult.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionSignatureThatTakesActualResult extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/BasicCustomFiles.php b/seed/php-model/trace/src/V2/V3/Problem/BasicCustomFiles.php index c61350ad841..c1569785a6a 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/BasicCustomFiles.php +++ b/seed/php-model/trace/src/V2/V3/Problem/BasicCustomFiles.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class BasicCustomFiles extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/BasicTestCaseTemplate.php b/seed/php-model/trace/src/V2/V3/Problem/BasicTestCaseTemplate.php index 582daf5cad8..01217713dc2 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/BasicTestCaseTemplate.php +++ b/seed/php-model/trace/src/V2/V3/Problem/BasicTestCaseTemplate.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BasicTestCaseTemplate extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/CreateProblemRequestV2.php b/seed/php-model/trace/src/V2/V3/Problem/CreateProblemRequestV2.php index ae14d6bec1a..48ca7df51ec 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/CreateProblemRequestV2.php +++ b/seed/php-model/trace/src/V2/V3/Problem/CreateProblemRequestV2.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Problem\ProblemDescription; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Commons\Language; class CreateProblemRequestV2 extends SerializableType diff --git a/seed/php-model/trace/src/V2/V3/Problem/DeepEqualityCorrectnessCheck.php b/seed/php-model/trace/src/V2/V3/Problem/DeepEqualityCorrectnessCheck.php index d6ab1822ee6..4df56631c85 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/DeepEqualityCorrectnessCheck.php +++ b/seed/php-model/trace/src/V2/V3/Problem/DeepEqualityCorrectnessCheck.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DeepEqualityCorrectnessCheck extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/DefaultProvidedFile.php b/seed/php-model/trace/src/V2/V3/Problem/DefaultProvidedFile.php index a38c80301c6..2cec4faf684 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/DefaultProvidedFile.php +++ b/seed/php-model/trace/src/V2/V3/Problem/DefaultProvidedFile.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DefaultProvidedFile extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/FileInfoV2.php b/seed/php-model/trace/src/V2/V3/Problem/FileInfoV2.php index d72ff62a9fd..c71fa0acb92 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/FileInfoV2.php +++ b/seed/php-model/trace/src/V2/V3/Problem/FileInfoV2.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FileInfoV2 extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/Files.php b/seed/php-model/trace/src/V2/V3/Problem/Files.php index 8b13d49aaea..97719908dec 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/Files.php +++ b/seed/php-model/trace/src/V2/V3/Problem/Files.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Files extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/FunctionImplementation.php b/seed/php-model/trace/src/V2/V3/Problem/FunctionImplementation.php index 25eb6003f2d..99f01ff5351 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/FunctionImplementation.php +++ b/seed/php-model/trace/src/V2/V3/Problem/FunctionImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FunctionImplementation extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/FunctionImplementationForMultipleLanguages.php b/seed/php-model/trace/src/V2/V3/Problem/FunctionImplementationForMultipleLanguages.php index 1e8d68698df..8ac8c08d529 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/FunctionImplementationForMultipleLanguages.php +++ b/seed/php-model/trace/src/V2/V3/Problem/FunctionImplementationForMultipleLanguages.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class FunctionImplementationForMultipleLanguages extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/GeneratedFiles.php b/seed/php-model/trace/src/V2/V3/Problem/GeneratedFiles.php index c33bfd83ace..fb51de2817c 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/GeneratedFiles.php +++ b/seed/php-model/trace/src/V2/V3/Problem/GeneratedFiles.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GeneratedFiles extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/GetBasicSolutionFileRequest.php b/seed/php-model/trace/src/V2/V3/Problem/GetBasicSolutionFileRequest.php index d24a4a52f1c..b5d56191ac9 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/GetBasicSolutionFileRequest.php +++ b/seed/php-model/trace/src/V2/V3/Problem/GetBasicSolutionFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetBasicSolutionFileRequest extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/GetBasicSolutionFileResponse.php b/seed/php-model/trace/src/V2/V3/Problem/GetBasicSolutionFileResponse.php index ace2a83160e..48078067a14 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/GetBasicSolutionFileResponse.php +++ b/seed/php-model/trace/src/V2/V3/Problem/GetBasicSolutionFileResponse.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetBasicSolutionFileResponse extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/GetFunctionSignatureRequest.php b/seed/php-model/trace/src/V2/V3/Problem/GetFunctionSignatureRequest.php index f4ae26d15db..bedba3ac35b 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/GetFunctionSignatureRequest.php +++ b/seed/php-model/trace/src/V2/V3/Problem/GetFunctionSignatureRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetFunctionSignatureRequest extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/GetFunctionSignatureResponse.php b/seed/php-model/trace/src/V2/V3/Problem/GetFunctionSignatureResponse.php index 8521fa88a40..bf61d70ee31 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/GetFunctionSignatureResponse.php +++ b/seed/php-model/trace/src/V2/V3/Problem/GetFunctionSignatureResponse.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetFunctionSignatureResponse extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/GetGeneratedTestCaseFileRequest.php b/seed/php-model/trace/src/V2/V3/Problem/GetGeneratedTestCaseFileRequest.php index 9d469caeb2f..be036dfeae4 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/GetGeneratedTestCaseFileRequest.php +++ b/seed/php-model/trace/src/V2/V3/Problem/GetGeneratedTestCaseFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetGeneratedTestCaseFileRequest extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/GetGeneratedTestCaseTemplateFileRequest.php b/seed/php-model/trace/src/V2/V3/Problem/GetGeneratedTestCaseTemplateFileRequest.php index 41dfd351e82..38dc263c20d 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/GetGeneratedTestCaseTemplateFileRequest.php +++ b/seed/php-model/trace/src/V2/V3/Problem/GetGeneratedTestCaseTemplateFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetGeneratedTestCaseTemplateFileRequest extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/LightweightProblemInfoV2.php b/seed/php-model/trace/src/V2/V3/Problem/LightweightProblemInfoV2.php index e19f388439d..bb280dcc36c 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/LightweightProblemInfoV2.php +++ b/seed/php-model/trace/src/V2/V3/Problem/LightweightProblemInfoV2.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class LightweightProblemInfoV2 extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/NonVoidFunctionDefinition.php b/seed/php-model/trace/src/V2/V3/Problem/NonVoidFunctionDefinition.php index f22ccc4b234..07576b42ee5 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/NonVoidFunctionDefinition.php +++ b/seed/php-model/trace/src/V2/V3/Problem/NonVoidFunctionDefinition.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NonVoidFunctionDefinition extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/NonVoidFunctionSignature.php b/seed/php-model/trace/src/V2/V3/Problem/NonVoidFunctionSignature.php index e1f6facc0a9..d41c7c156f5 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/NonVoidFunctionSignature.php +++ b/seed/php-model/trace/src/V2/V3/Problem/NonVoidFunctionSignature.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class NonVoidFunctionSignature extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/Parameter.php b/seed/php-model/trace/src/V2/V3/Problem/Parameter.php index ab0c15a3de9..301fbc9ed13 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/Parameter.php +++ b/seed/php-model/trace/src/V2/V3/Problem/Parameter.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Parameter extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/ProblemInfoV2.php b/seed/php-model/trace/src/V2/V3/Problem/ProblemInfoV2.php index 37da3d46f42..79c5ca16d18 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/ProblemInfoV2.php +++ b/seed/php-model/trace/src/V2/V3/Problem/ProblemInfoV2.php @@ -2,11 +2,11 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Problem\ProblemDescription; use Seed\Commons\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class ProblemInfoV2 extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/TestCaseExpects.php b/seed/php-model/trace/src/V2/V3/Problem/TestCaseExpects.php index 604d30dbb24..0a17fa03da2 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/TestCaseExpects.php +++ b/seed/php-model/trace/src/V2/V3/Problem/TestCaseExpects.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseExpects extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/TestCaseImplementation.php b/seed/php-model/trace/src/V2/V3/Problem/TestCaseImplementation.php index 55ed385d26d..d47cf44c5a7 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/TestCaseImplementation.php +++ b/seed/php-model/trace/src/V2/V3/Problem/TestCaseImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseImplementation extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/TestCaseImplementationDescription.php b/seed/php-model/trace/src/V2/V3/Problem/TestCaseImplementationDescription.php index 33e06c2d619..02cd5c5e57e 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/TestCaseImplementationDescription.php +++ b/seed/php-model/trace/src/V2/V3/Problem/TestCaseImplementationDescription.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCaseImplementationDescription extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/TestCaseMetadata.php b/seed/php-model/trace/src/V2/V3/Problem/TestCaseMetadata.php index 6311b6a8391..006ea5b4524 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/TestCaseMetadata.php +++ b/seed/php-model/trace/src/V2/V3/Problem/TestCaseMetadata.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseMetadata extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/TestCaseTemplate.php b/seed/php-model/trace/src/V2/V3/Problem/TestCaseTemplate.php index a1bf7f6abf8..65f3aa2a698 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/TestCaseTemplate.php +++ b/seed/php-model/trace/src/V2/V3/Problem/TestCaseTemplate.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseTemplate extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/TestCaseV2.php b/seed/php-model/trace/src/V2/V3/Problem/TestCaseV2.php index 0862c099ac9..9b582ab5e3e 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/TestCaseV2.php +++ b/seed/php-model/trace/src/V2/V3/Problem/TestCaseV2.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCaseV2 extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/TestCaseWithActualResultImplementation.php b/seed/php-model/trace/src/V2/V3/Problem/TestCaseWithActualResultImplementation.php index 31c3e39a367..9fd69cde7c5 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/TestCaseWithActualResultImplementation.php +++ b/seed/php-model/trace/src/V2/V3/Problem/TestCaseWithActualResultImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseWithActualResultImplementation extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionDefinition.php b/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionDefinition.php index b51848ac888..9eb786818e5 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionDefinition.php +++ b/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionDefinition.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionDefinition extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionDefinitionThatTakesActualResult.php b/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionDefinitionThatTakesActualResult.php index cb8c259b442..f53f4e03bb2 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionDefinitionThatTakesActualResult.php +++ b/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionDefinitionThatTakesActualResult.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; /** * The generated signature will include an additional param, actualResult diff --git a/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionSignature.php b/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionSignature.php index a2fb2d72530..f99b39bd42b 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionSignature.php +++ b/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionSignature.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionSignature extends SerializableType { diff --git a/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionSignatureThatTakesActualResult.php b/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionSignatureThatTakesActualResult.php index 523e672f129..8c2df0b9bed 100644 --- a/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionSignatureThatTakesActualResult.php +++ b/seed/php-model/trace/src/V2/V3/Problem/VoidFunctionSignatureThatTakesActualResult.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionSignatureThatTakesActualResult extends SerializableType { diff --git a/seed/php-model/trace/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/trace/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/EnumTest.php b/seed/php-model/trace/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/trace/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/trace/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/trace/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/trace/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/trace/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/trace/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/trace/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/trace/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/trace/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/trace/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/trace/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/trace/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/TestTypeTest.php b/seed/php-model/trace/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/trace/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/trace/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/trace/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/trace/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/src/Core/ArrayType.php b/seed/php-model/undiscriminated-unions/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/undiscriminated-unions/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Constant.php b/seed/php-model/undiscriminated-unions/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/undiscriminated-unions/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Json/JsonDeserializer.php b/seed/php-model/undiscriminated-unions/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Json/JsonEncoder.php b/seed/php-model/undiscriminated-unions/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Json/SerializableType.php b/seed/php-model/undiscriminated-unions/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Json/Utils.php b/seed/php-model/undiscriminated-unions/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/undiscriminated-unions/src/Core/JsonDecoder.php b/seed/php-model/undiscriminated-unions/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/undiscriminated-unions/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/undiscriminated-unions/src/Core/JsonDeserializer.php b/seed/php-model/undiscriminated-unions/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/undiscriminated-unions/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/undiscriminated-unions/src/Core/JsonEncoder.php b/seed/php-model/undiscriminated-unions/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/undiscriminated-unions/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/undiscriminated-unions/src/Core/SerializableType.php b/seed/php-model/undiscriminated-unions/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/undiscriminated-unions/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Types/ArrayType.php b/seed/php-model/undiscriminated-unions/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/undiscriminated-unions/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Types/Constant.php b/seed/php-model/undiscriminated-unions/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/undiscriminated-unions/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Union.php b/seed/php-model/undiscriminated-unions/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/undiscriminated-unions/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/undiscriminated-unions/src/Core/Utils.php b/seed/php-model/undiscriminated-unions/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/undiscriminated-unions/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/EnumTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/TestTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/unions/src/Core/ArrayType.php b/seed/php-model/unions/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/unions/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/unions/src/Core/Constant.php b/seed/php-model/unions/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/unions/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/unions/src/Core/Json/JsonDeserializer.php b/seed/php-model/unions/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/unions/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/unions/src/Core/Json/JsonEncoder.php b/seed/php-model/unions/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/unions/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/unions/src/Core/Json/SerializableType.php b/seed/php-model/unions/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/unions/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/unions/src/Core/Json/Utils.php b/seed/php-model/unions/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/unions/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/unions/src/Core/JsonDecoder.php b/seed/php-model/unions/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/unions/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/unions/src/Core/JsonDeserializer.php b/seed/php-model/unions/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/unions/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/unions/src/Core/JsonEncoder.php b/seed/php-model/unions/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/unions/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/unions/src/Core/SerializableType.php b/seed/php-model/unions/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/unions/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/unions/src/Core/Types/ArrayType.php b/seed/php-model/unions/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/unions/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/unions/src/Core/Types/Constant.php b/seed/php-model/unions/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/unions/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/unions/src/Core/Union.php b/seed/php-model/unions/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/unions/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/unions/src/Core/Utils.php b/seed/php-model/unions/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/unions/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/unions/src/Types/Bar.php b/seed/php-model/unions/src/Types/Bar.php index 475d1be9162..5eecab4c2c7 100644 --- a/seed/php-model/unions/src/Types/Bar.php +++ b/seed/php-model/unions/src/Types/Bar.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Bar extends SerializableType { diff --git a/seed/php-model/unions/src/Types/Foo.php b/seed/php-model/unions/src/Types/Foo.php index 94b4a3d9f9d..6ffff133ac3 100644 --- a/seed/php-model/unions/src/Types/Foo.php +++ b/seed/php-model/unions/src/Types/Foo.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Foo extends SerializableType { diff --git a/seed/php-model/unions/src/Union/Circle.php b/seed/php-model/unions/src/Union/Circle.php index 849ffd6e68b..d3e8be921d8 100644 --- a/seed/php-model/unions/src/Union/Circle.php +++ b/seed/php-model/unions/src/Union/Circle.php @@ -2,8 +2,8 @@ namespace Seed\Union; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Circle extends SerializableType { diff --git a/seed/php-model/unions/src/Union/GetShapeRequest.php b/seed/php-model/unions/src/Union/GetShapeRequest.php index 4a864ccb5f0..3b7d9e49166 100644 --- a/seed/php-model/unions/src/Union/GetShapeRequest.php +++ b/seed/php-model/unions/src/Union/GetShapeRequest.php @@ -2,8 +2,8 @@ namespace Seed\Union; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetShapeRequest extends SerializableType { diff --git a/seed/php-model/unions/src/Union/Square.php b/seed/php-model/unions/src/Union/Square.php index 53d0838e6fe..33f0b378d77 100644 --- a/seed/php-model/unions/src/Union/Square.php +++ b/seed/php-model/unions/src/Union/Square.php @@ -2,8 +2,8 @@ namespace Seed\Union; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Square extends SerializableType { diff --git a/seed/php-model/unions/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/unions/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/EnumTest.php b/seed/php-model/unions/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/unions/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/unions/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/unions/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/unions/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/unions/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/unions/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/unions/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/unions/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/unions/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/unions/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/TestTypeTest.php b/seed/php-model/unions/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/unions/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/unions/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/unions/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/unions/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/unknown/src/Core/ArrayType.php b/seed/php-model/unknown/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/unknown/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/unknown/src/Core/Constant.php b/seed/php-model/unknown/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/unknown/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/unknown/src/Core/Json/JsonDeserializer.php b/seed/php-model/unknown/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/unknown/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/unknown/src/Core/Json/JsonEncoder.php b/seed/php-model/unknown/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/unknown/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/unknown/src/Core/Json/SerializableType.php b/seed/php-model/unknown/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/unknown/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/unknown/src/Core/Json/Utils.php b/seed/php-model/unknown/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/unknown/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/unknown/src/Core/JsonDecoder.php b/seed/php-model/unknown/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/unknown/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/unknown/src/Core/JsonDeserializer.php b/seed/php-model/unknown/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/unknown/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/unknown/src/Core/JsonEncoder.php b/seed/php-model/unknown/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/unknown/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/unknown/src/Core/SerializableType.php b/seed/php-model/unknown/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/unknown/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/unknown/src/Core/Types/ArrayType.php b/seed/php-model/unknown/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/unknown/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/unknown/src/Core/Types/Constant.php b/seed/php-model/unknown/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/unknown/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/unknown/src/Core/Union.php b/seed/php-model/unknown/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/unknown/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/unknown/src/Core/Utils.php b/seed/php-model/unknown/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/unknown/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/unknown/src/Unknown/MyObject.php b/seed/php-model/unknown/src/Unknown/MyObject.php index 7097f40c612..305ce32a9d2 100644 --- a/seed/php-model/unknown/src/Unknown/MyObject.php +++ b/seed/php-model/unknown/src/Unknown/MyObject.php @@ -2,8 +2,8 @@ namespace Seed\Unknown; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class MyObject extends SerializableType { diff --git a/seed/php-model/unknown/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/unknown/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/EnumTest.php b/seed/php-model/unknown/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/unknown/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/unknown/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/unknown/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/unknown/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/TestTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/unknown/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/unknown/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/unknown/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/validation/src/Core/ArrayType.php b/seed/php-model/validation/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/validation/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/validation/src/Core/Constant.php b/seed/php-model/validation/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/validation/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/validation/src/Core/Json/JsonDeserializer.php b/seed/php-model/validation/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/validation/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/validation/src/Core/Json/JsonEncoder.php b/seed/php-model/validation/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/validation/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/validation/src/Core/Json/SerializableType.php b/seed/php-model/validation/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/validation/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/validation/src/Core/Json/Utils.php b/seed/php-model/validation/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/validation/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/validation/src/Core/JsonDecoder.php b/seed/php-model/validation/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/validation/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/validation/src/Core/JsonDeserializer.php b/seed/php-model/validation/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/validation/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/validation/src/Core/JsonEncoder.php b/seed/php-model/validation/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/validation/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/validation/src/Core/SerializableType.php b/seed/php-model/validation/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/validation/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/validation/src/Core/Types/ArrayType.php b/seed/php-model/validation/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/validation/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/validation/src/Core/Types/Constant.php b/seed/php-model/validation/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/validation/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/validation/src/Core/Union.php b/seed/php-model/validation/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/validation/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/validation/src/Core/Utils.php b/seed/php-model/validation/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/validation/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/validation/src/Type.php b/seed/php-model/validation/src/Type.php index 96f14909279..726302c9532 100644 --- a/seed/php-model/validation/src/Type.php +++ b/seed/php-model/validation/src/Type.php @@ -2,8 +2,8 @@ namespace Seed; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * Defines properties with default values and validation rules. diff --git a/seed/php-model/validation/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/validation/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/EnumTest.php b/seed/php-model/validation/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/validation/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/validation/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/validation/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/validation/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/validation/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/validation/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/validation/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/validation/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/validation/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/validation/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/validation/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/validation/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/TestTypeTest.php b/seed/php-model/validation/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/validation/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/validation/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/validation/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/validation/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/variables/src/Core/ArrayType.php b/seed/php-model/variables/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/variables/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/variables/src/Core/Constant.php b/seed/php-model/variables/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/variables/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/variables/src/Core/Json/JsonDeserializer.php b/seed/php-model/variables/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/variables/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/variables/src/Core/Json/JsonEncoder.php b/seed/php-model/variables/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/variables/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/variables/src/Core/Json/SerializableType.php b/seed/php-model/variables/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/variables/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/variables/src/Core/Json/Utils.php b/seed/php-model/variables/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/variables/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/variables/src/Core/JsonDecoder.php b/seed/php-model/variables/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/variables/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/variables/src/Core/JsonDeserializer.php b/seed/php-model/variables/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/variables/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/variables/src/Core/JsonEncoder.php b/seed/php-model/variables/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/variables/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/variables/src/Core/SerializableType.php b/seed/php-model/variables/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/variables/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/variables/src/Core/Types/ArrayType.php b/seed/php-model/variables/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/variables/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/variables/src/Core/Types/Constant.php b/seed/php-model/variables/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/variables/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/variables/src/Core/Union.php b/seed/php-model/variables/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/variables/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/variables/src/Core/Utils.php b/seed/php-model/variables/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/variables/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/variables/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/EnumTest.php b/seed/php-model/variables/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/variables/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/variables/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/variables/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/variables/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/variables/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/variables/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/variables/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/variables/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/variables/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/variables/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/TestTypeTest.php b/seed/php-model/variables/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/variables/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/variables/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/variables/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/version-no-default/src/Core/ArrayType.php b/seed/php-model/version-no-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/version-no-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/version-no-default/src/Core/Constant.php b/seed/php-model/version-no-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/version-no-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/version-no-default/src/Core/Json/JsonDeserializer.php b/seed/php-model/version-no-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/version-no-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/version-no-default/src/Core/Json/JsonEncoder.php b/seed/php-model/version-no-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/version-no-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/version-no-default/src/Core/Json/SerializableType.php b/seed/php-model/version-no-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/version-no-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/version-no-default/src/Core/Json/Utils.php b/seed/php-model/version-no-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/version-no-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/version-no-default/src/Core/JsonDecoder.php b/seed/php-model/version-no-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/version-no-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/version-no-default/src/Core/JsonDeserializer.php b/seed/php-model/version-no-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/version-no-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/version-no-default/src/Core/JsonEncoder.php b/seed/php-model/version-no-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/version-no-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/version-no-default/src/Core/SerializableType.php b/seed/php-model/version-no-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/version-no-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/version-no-default/src/Core/Types/ArrayType.php b/seed/php-model/version-no-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/version-no-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/version-no-default/src/Core/Types/Constant.php b/seed/php-model/version-no-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/version-no-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/version-no-default/src/Core/Union.php b/seed/php-model/version-no-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/version-no-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/version-no-default/src/Core/Utils.php b/seed/php-model/version-no-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/version-no-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/version-no-default/src/User/User.php b/seed/php-model/version-no-default/src/User/User.php index 7f38b952d59..fc815c903d5 100644 --- a/seed/php-model/version-no-default/src/User/User.php +++ b/seed/php-model/version-no-default/src/User/User.php @@ -2,8 +2,8 @@ namespace Seed\User; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-model/version-no-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/version-no-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/EnumTest.php b/seed/php-model/version-no-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/version-no-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/version-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/version-no-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/TestTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/version/src/Core/ArrayType.php b/seed/php-model/version/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/version/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/version/src/Core/Constant.php b/seed/php-model/version/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/version/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/version/src/Core/Json/JsonDeserializer.php b/seed/php-model/version/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/version/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/version/src/Core/Json/JsonEncoder.php b/seed/php-model/version/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/version/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/version/src/Core/Json/SerializableType.php b/seed/php-model/version/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/version/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/version/src/Core/Json/Utils.php b/seed/php-model/version/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/version/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/version/src/Core/JsonDecoder.php b/seed/php-model/version/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/version/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/version/src/Core/JsonDeserializer.php b/seed/php-model/version/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/version/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/version/src/Core/JsonEncoder.php b/seed/php-model/version/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/version/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/version/src/Core/SerializableType.php b/seed/php-model/version/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/version/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/version/src/Core/Types/ArrayType.php b/seed/php-model/version/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/version/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/version/src/Core/Types/Constant.php b/seed/php-model/version/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/version/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/version/src/Core/Union.php b/seed/php-model/version/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/version/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/version/src/Core/Utils.php b/seed/php-model/version/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/version/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/version/src/User/User.php b/seed/php-model/version/src/User/User.php index 7f38b952d59..fc815c903d5 100644 --- a/seed/php-model/version/src/User/User.php +++ b/seed/php-model/version/src/User/User.php @@ -2,8 +2,8 @@ namespace Seed\User; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-model/version/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/version/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/version/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/version/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/EnumTest.php b/seed/php-model/version/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/version/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/version/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/version/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/version/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/version/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/version/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/version/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/version/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/version/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/version/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/version/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/version/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/version/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/version/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/version/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/version/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/version/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/version/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/version/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/TestTypeTest.php b/seed/php-model/version/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/version/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/version/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/version/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/version/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/version/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/version/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-model/websocket/src/Core/ArrayType.php b/seed/php-model/websocket/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-model/websocket/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-model/websocket/src/Core/Constant.php b/seed/php-model/websocket/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-model/websocket/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/websocket/src/Core/Json/JsonDeserializer.php b/seed/php-model/websocket/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-model/websocket/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/websocket/src/Core/Json/JsonEncoder.php b/seed/php-model/websocket/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/websocket/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/websocket/src/Core/Json/SerializableType.php b/seed/php-model/websocket/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-model/websocket/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/websocket/src/Core/Json/Utils.php b/seed/php-model/websocket/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/websocket/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/websocket/src/Core/JsonDecoder.php b/seed/php-model/websocket/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-model/websocket/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-model/websocket/src/Core/JsonDeserializer.php b/seed/php-model/websocket/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-model/websocket/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/websocket/src/Core/JsonEncoder.php b/seed/php-model/websocket/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-model/websocket/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-model/websocket/src/Core/SerializableType.php b/seed/php-model/websocket/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-model/websocket/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-model/websocket/src/Core/Types/ArrayType.php b/seed/php-model/websocket/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/websocket/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/websocket/src/Core/Types/Constant.php b/seed/php-model/websocket/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/websocket/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/websocket/src/Core/Union.php b/seed/php-model/websocket/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-model/websocket/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-model/websocket/src/Core/Utils.php b/seed/php-model/websocket/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-model/websocket/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/EmptyArraysTest.php b/seed/php-model/websocket/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/EnumTest.php b/seed/php-model/websocket/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/InvalidTypesTest.php b/seed/php-model/websocket/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-model/websocket/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/websocket/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/ScalarTypesTest.php b/seed/php-model/websocket/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/TestTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-model/websocket/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-model/websocket/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-model/websocket/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/ArrayType.php b/seed/php-sdk/alias-extends/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/BaseApiRequest.php b/seed/php-sdk/alias-extends/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/alias-extends/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Client/HttpMethod.php b/seed/php-sdk/alias-extends/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Constant.php b/seed/php-sdk/alias-extends/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Json/JsonDecoder.php b/seed/php-sdk/alias-extends/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/alias-extends/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Json/JsonEncoder.php b/seed/php-sdk/alias-extends/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Json/SerializableType.php b/seed/php-sdk/alias-extends/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Json/Utils.php b/seed/php-sdk/alias-extends/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/JsonApiRequest.php b/seed/php-sdk/alias-extends/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/JsonDecoder.php b/seed/php-sdk/alias-extends/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/JsonDeserializer.php b/seed/php-sdk/alias-extends/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/JsonEncoder.php b/seed/php-sdk/alias-extends/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/RawClient.php b/seed/php-sdk/alias-extends/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/SerializableType.php b/seed/php-sdk/alias-extends/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/Types/ArrayType.php b/seed/php-sdk/alias-extends/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Types/Constant.php b/seed/php-sdk/alias-extends/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/alias-extends/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/alias-extends/src/Core/Union.php b/seed/php-sdk/alias-extends/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/alias-extends/src/Core/Utils.php b/seed/php-sdk/alias-extends/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/alias-extends/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/alias-extends/src/Requests/InlinedChildRequest.php b/seed/php-sdk/alias-extends/src/Requests/InlinedChildRequest.php index 66a77e495f2..e1f0d3fbf7d 100644 --- a/seed/php-sdk/alias-extends/src/Requests/InlinedChildRequest.php +++ b/seed/php-sdk/alias-extends/src/Requests/InlinedChildRequest.php @@ -2,8 +2,8 @@ namespace Seed\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class InlinedChildRequest extends SerializableType { diff --git a/seed/php-sdk/alias-extends/src/SeedClient.php b/seed/php-sdk/alias-extends/src/SeedClient.php index b0f258291e0..04eb1238537 100644 --- a/seed/php-sdk/alias-extends/src/SeedClient.php +++ b/seed/php-sdk/alias-extends/src/SeedClient.php @@ -3,12 +3,12 @@ namespace Seed; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Requests\InlinedChildRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class SeedClient diff --git a/seed/php-sdk/alias-extends/src/Types/Child.php b/seed/php-sdk/alias-extends/src/Types/Child.php index 2b3f5957f33..e31712505bc 100644 --- a/seed/php-sdk/alias-extends/src/Types/Child.php +++ b/seed/php-sdk/alias-extends/src/Types/Child.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Child extends SerializableType { diff --git a/seed/php-sdk/alias-extends/src/Types/Parent_.php b/seed/php-sdk/alias-extends/src/Types/Parent_.php index 3105ebf922d..d2edc14dc15 100644 --- a/seed/php-sdk/alias-extends/src/Types/Parent_.php +++ b/seed/php-sdk/alias-extends/src/Types/Parent_.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Parent_ extends SerializableType { diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/EnumTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/alias-extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/alias-extends/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/alias/src/Core/ArrayType.php b/seed/php-sdk/alias/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/alias/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/alias/src/Core/BaseApiRequest.php b/seed/php-sdk/alias/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/alias/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/alias/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/alias/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/alias/src/Core/Client/HttpMethod.php b/seed/php-sdk/alias/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/alias/src/Core/Constant.php b/seed/php-sdk/alias/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/alias/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/alias/src/Core/Json/JsonDecoder.php b/seed/php-sdk/alias/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/alias/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/alias/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/alias/src/Core/Json/JsonEncoder.php b/seed/php-sdk/alias/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/alias/src/Core/Json/SerializableType.php b/seed/php-sdk/alias/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/alias/src/Core/Json/Utils.php b/seed/php-sdk/alias/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/alias/src/Core/JsonApiRequest.php b/seed/php-sdk/alias/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/alias/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/alias/src/Core/JsonDecoder.php b/seed/php-sdk/alias/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/alias/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/alias/src/Core/JsonDeserializer.php b/seed/php-sdk/alias/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/alias/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/alias/src/Core/JsonEncoder.php b/seed/php-sdk/alias/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/alias/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/alias/src/Core/RawClient.php b/seed/php-sdk/alias/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/alias/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/alias/src/Core/SerializableType.php b/seed/php-sdk/alias/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/alias/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/alias/src/Core/Types/ArrayType.php b/seed/php-sdk/alias/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/alias/src/Core/Types/Constant.php b/seed/php-sdk/alias/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/alias/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/alias/src/Core/Union.php b/seed/php-sdk/alias/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/alias/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/alias/src/Core/Utils.php b/seed/php-sdk/alias/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/alias/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/alias/src/SeedClient.php b/seed/php-sdk/alias/src/SeedClient.php index 5f3f740a222..14fc5b621ba 100644 --- a/seed/php-sdk/alias/src/SeedClient.php +++ b/seed/php-sdk/alias/src/SeedClient.php @@ -3,11 +3,11 @@ namespace Seed; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class SeedClient diff --git a/seed/php-sdk/alias/src/Types/Type.php b/seed/php-sdk/alias/src/Types/Type.php index 927b5fa861e..c914fbbda8c 100644 --- a/seed/php-sdk/alias/src/Types/Type.php +++ b/seed/php-sdk/alias/src/Types/Type.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * A simple type with just a name. diff --git a/seed/php-sdk/alias/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/alias/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/alias/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/EnumTest.php b/seed/php-sdk/alias/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/alias/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/alias/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/alias/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/alias/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/alias/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/alias/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/alias/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/alias/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/any-auth/src/Auth/AuthClient.php b/seed/php-sdk/any-auth/src/Auth/AuthClient.php index 7e6187de604..73ac83c5e41 100644 --- a/seed/php-sdk/any-auth/src/Auth/AuthClient.php +++ b/seed/php-sdk/any-auth/src/Auth/AuthClient.php @@ -2,13 +2,13 @@ namespace Seed\Auth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Auth\Requests\GetTokenRequest; use Seed\Auth\Types\TokenResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/any-auth/src/Auth/Requests/GetTokenRequest.php b/seed/php-sdk/any-auth/src/Auth/Requests/GetTokenRequest.php index b1536a44943..db7faf22c19 100644 --- a/seed/php-sdk/any-auth/src/Auth/Requests/GetTokenRequest.php +++ b/seed/php-sdk/any-auth/src/Auth/Requests/GetTokenRequest.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetTokenRequest extends SerializableType { diff --git a/seed/php-sdk/any-auth/src/Auth/Types/TokenResponse.php b/seed/php-sdk/any-auth/src/Auth/Types/TokenResponse.php index f4dd25eedf1..b1eeb7a9e98 100644 --- a/seed/php-sdk/any-auth/src/Auth/Types/TokenResponse.php +++ b/seed/php-sdk/any-auth/src/Auth/Types/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-sdk/any-auth/src/Core/ArrayType.php b/seed/php-sdk/any-auth/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/any-auth/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/any-auth/src/Core/BaseApiRequest.php b/seed/php-sdk/any-auth/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/any-auth/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/any-auth/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/any-auth/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Client/HttpMethod.php b/seed/php-sdk/any-auth/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Constant.php b/seed/php-sdk/any-auth/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/any-auth/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Json/JsonDecoder.php b/seed/php-sdk/any-auth/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/any-auth/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Json/JsonEncoder.php b/seed/php-sdk/any-auth/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Json/SerializableType.php b/seed/php-sdk/any-auth/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Json/Utils.php b/seed/php-sdk/any-auth/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/any-auth/src/Core/JsonApiRequest.php b/seed/php-sdk/any-auth/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/any-auth/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/any-auth/src/Core/JsonDecoder.php b/seed/php-sdk/any-auth/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/any-auth/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/any-auth/src/Core/JsonDeserializer.php b/seed/php-sdk/any-auth/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/any-auth/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/any-auth/src/Core/JsonEncoder.php b/seed/php-sdk/any-auth/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/any-auth/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/any-auth/src/Core/RawClient.php b/seed/php-sdk/any-auth/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/any-auth/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/any-auth/src/Core/SerializableType.php b/seed/php-sdk/any-auth/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/any-auth/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/any-auth/src/Core/Types/ArrayType.php b/seed/php-sdk/any-auth/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Types/Constant.php b/seed/php-sdk/any-auth/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/any-auth/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/any-auth/src/Core/Union.php b/seed/php-sdk/any-auth/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/any-auth/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/any-auth/src/Core/Utils.php b/seed/php-sdk/any-auth/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/any-auth/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/any-auth/src/SeedClient.php b/seed/php-sdk/any-auth/src/SeedClient.php index 552ae22be77..2a0dd0267df 100644 --- a/seed/php-sdk/any-auth/src/SeedClient.php +++ b/seed/php-sdk/any-auth/src/SeedClient.php @@ -5,7 +5,7 @@ use Seed\Auth\AuthClient; use Seed\User\UserClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Exception; class SeedClient diff --git a/seed/php-sdk/any-auth/src/User/Types/User.php b/seed/php-sdk/any-auth/src/User/Types/User.php index af657430e5c..5c95ec2354c 100644 --- a/seed/php-sdk/any-auth/src/User/Types/User.php +++ b/seed/php-sdk/any-auth/src/User/Types/User.php @@ -2,8 +2,8 @@ namespace Seed\User\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-sdk/any-auth/src/User/UserClient.php b/seed/php-sdk/any-auth/src/User/UserClient.php index e259b63efae..2e72403818c 100644 --- a/seed/php-sdk/any-auth/src/User/UserClient.php +++ b/seed/php-sdk/any-auth/src/User/UserClient.php @@ -2,13 +2,13 @@ namespace Seed\User; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\User\Types\User; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/EnumTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/any-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/any-auth/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/ArrayType.php b/seed/php-sdk/api-wide-base-path/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/BaseApiRequest.php b/seed/php-sdk/api-wide-base-path/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/api-wide-base-path/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Client/HttpMethod.php b/seed/php-sdk/api-wide-base-path/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Constant.php b/seed/php-sdk/api-wide-base-path/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonDecoder.php b/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonEncoder.php b/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Json/SerializableType.php b/seed/php-sdk/api-wide-base-path/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Json/Utils.php b/seed/php-sdk/api-wide-base-path/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/JsonApiRequest.php b/seed/php-sdk/api-wide-base-path/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/JsonDecoder.php b/seed/php-sdk/api-wide-base-path/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/JsonDeserializer.php b/seed/php-sdk/api-wide-base-path/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/JsonEncoder.php b/seed/php-sdk/api-wide-base-path/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/RawClient.php b/seed/php-sdk/api-wide-base-path/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/SerializableType.php b/seed/php-sdk/api-wide-base-path/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Types/ArrayType.php b/seed/php-sdk/api-wide-base-path/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Types/Constant.php b/seed/php-sdk/api-wide-base-path/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Union.php b/seed/php-sdk/api-wide-base-path/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/Core/Utils.php b/seed/php-sdk/api-wide-base-path/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/api-wide-base-path/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/api-wide-base-path/src/SeedClient.php b/seed/php-sdk/api-wide-base-path/src/SeedClient.php index 5f052555c44..4ca47b660f7 100644 --- a/seed/php-sdk/api-wide-base-path/src/SeedClient.php +++ b/seed/php-sdk/api-wide-base-path/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/api-wide-base-path/src/Service/ServiceClient.php b/seed/php-sdk/api-wide-base-path/src/Service/ServiceClient.php index 43671304697..fa903c4c164 100644 --- a/seed/php-sdk/api-wide-base-path/src/Service/ServiceClient.php +++ b/seed/php-sdk/api-wide-base-path/src/Service/ServiceClient.php @@ -2,11 +2,11 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class ServiceClient diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/EnumTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/api-wide-base-path/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/audiences/src/Core/ArrayType.php b/seed/php-sdk/audiences/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/audiences/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/audiences/src/Core/BaseApiRequest.php b/seed/php-sdk/audiences/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/audiences/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/audiences/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/audiences/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/audiences/src/Core/Client/HttpMethod.php b/seed/php-sdk/audiences/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/audiences/src/Core/Constant.php b/seed/php-sdk/audiences/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/audiences/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/audiences/src/Core/Json/JsonDecoder.php b/seed/php-sdk/audiences/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/audiences/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/audiences/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/audiences/src/Core/Json/JsonEncoder.php b/seed/php-sdk/audiences/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/audiences/src/Core/Json/SerializableType.php b/seed/php-sdk/audiences/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/audiences/src/Core/Json/Utils.php b/seed/php-sdk/audiences/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/audiences/src/Core/JsonApiRequest.php b/seed/php-sdk/audiences/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/audiences/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/audiences/src/Core/JsonDecoder.php b/seed/php-sdk/audiences/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/audiences/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/audiences/src/Core/JsonDeserializer.php b/seed/php-sdk/audiences/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/audiences/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/audiences/src/Core/JsonEncoder.php b/seed/php-sdk/audiences/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/audiences/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/audiences/src/Core/RawClient.php b/seed/php-sdk/audiences/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/audiences/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/audiences/src/Core/SerializableType.php b/seed/php-sdk/audiences/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/audiences/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/audiences/src/Core/Types/ArrayType.php b/seed/php-sdk/audiences/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/audiences/src/Core/Types/Constant.php b/seed/php-sdk/audiences/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/audiences/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/audiences/src/Core/Union.php b/seed/php-sdk/audiences/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/audiences/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/audiences/src/Core/Utils.php b/seed/php-sdk/audiences/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/audiences/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/audiences/src/FolderA/FolderAClient.php b/seed/php-sdk/audiences/src/FolderA/FolderAClient.php index e1124b6da8d..56b64f8553b 100644 --- a/seed/php-sdk/audiences/src/FolderA/FolderAClient.php +++ b/seed/php-sdk/audiences/src/FolderA/FolderAClient.php @@ -3,7 +3,7 @@ namespace Seed\FolderA; use Seed\FolderA\Service\ServiceClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class FolderAClient { diff --git a/seed/php-sdk/audiences/src/FolderA/Service/ServiceClient.php b/seed/php-sdk/audiences/src/FolderA/Service/ServiceClient.php index 9011e3e1ca9..c7c856fb630 100644 --- a/seed/php-sdk/audiences/src/FolderA/Service/ServiceClient.php +++ b/seed/php-sdk/audiences/src/FolderA/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\FolderA\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\FolderA\Service\Types\Response; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/audiences/src/FolderA/Service/Types/Response.php b/seed/php-sdk/audiences/src/FolderA/Service/Types/Response.php index af87a1efc2e..b9fa14d415c 100644 --- a/seed/php-sdk/audiences/src/FolderA/Service/Types/Response.php +++ b/seed/php-sdk/audiences/src/FolderA/Service/Types/Response.php @@ -2,9 +2,9 @@ namespace Seed\FolderA\Service\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderB\Common\Types\Foo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-sdk/audiences/src/FolderB/Common/Types/Foo.php b/seed/php-sdk/audiences/src/FolderB/Common/Types/Foo.php index 4dcf330be05..2f5735ccbc0 100644 --- a/seed/php-sdk/audiences/src/FolderB/Common/Types/Foo.php +++ b/seed/php-sdk/audiences/src/FolderB/Common/Types/Foo.php @@ -2,9 +2,9 @@ namespace Seed\FolderB\Common\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderC\Common\Types\FolderCFoo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Foo extends SerializableType { diff --git a/seed/php-sdk/audiences/src/FolderC/Common/Types/FolderCFoo.php b/seed/php-sdk/audiences/src/FolderC/Common/Types/FolderCFoo.php index 9b19dcaea5e..7be9fe23252 100644 --- a/seed/php-sdk/audiences/src/FolderC/Common/Types/FolderCFoo.php +++ b/seed/php-sdk/audiences/src/FolderC/Common/Types/FolderCFoo.php @@ -2,8 +2,8 @@ namespace Seed\FolderC\Common\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FolderCFoo extends SerializableType { diff --git a/seed/php-sdk/audiences/src/FolderD/FolderDClient.php b/seed/php-sdk/audiences/src/FolderD/FolderDClient.php index c8715e556d6..4d973ef9f72 100644 --- a/seed/php-sdk/audiences/src/FolderD/FolderDClient.php +++ b/seed/php-sdk/audiences/src/FolderD/FolderDClient.php @@ -3,7 +3,7 @@ namespace Seed\FolderD; use Seed\FolderD\Service\ServiceClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class FolderDClient { diff --git a/seed/php-sdk/audiences/src/FolderD/Service/ServiceClient.php b/seed/php-sdk/audiences/src/FolderD/Service/ServiceClient.php index c66f39f28e9..5ac46920477 100644 --- a/seed/php-sdk/audiences/src/FolderD/Service/ServiceClient.php +++ b/seed/php-sdk/audiences/src/FolderD/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\FolderD\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\FolderD\Service\Types\Response; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/audiences/src/FolderD/Service/Types/Response.php b/seed/php-sdk/audiences/src/FolderD/Service/Types/Response.php index 0e07548b323..403a29a4698 100644 --- a/seed/php-sdk/audiences/src/FolderD/Service/Types/Response.php +++ b/seed/php-sdk/audiences/src/FolderD/Service/Types/Response.php @@ -2,8 +2,8 @@ namespace Seed\FolderD\Service\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-sdk/audiences/src/Foo/FooClient.php b/seed/php-sdk/audiences/src/Foo/FooClient.php index 4af430995e6..98cb29b7679 100644 --- a/seed/php-sdk/audiences/src/Foo/FooClient.php +++ b/seed/php-sdk/audiences/src/Foo/FooClient.php @@ -2,13 +2,13 @@ namespace Seed\Foo; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Foo\Requests\FindRequest; use Seed\Foo\Types\ImportingType; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/audiences/src/Foo/Requests/FindRequest.php b/seed/php-sdk/audiences/src/Foo/Requests/FindRequest.php index e23f68a1a9a..5ca29173024 100644 --- a/seed/php-sdk/audiences/src/Foo/Requests/FindRequest.php +++ b/seed/php-sdk/audiences/src/Foo/Requests/FindRequest.php @@ -2,8 +2,8 @@ namespace Seed\Foo\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FindRequest extends SerializableType { diff --git a/seed/php-sdk/audiences/src/Foo/Types/FilteredType.php b/seed/php-sdk/audiences/src/Foo/Types/FilteredType.php index 5992c274dff..b9d7e7577cc 100644 --- a/seed/php-sdk/audiences/src/Foo/Types/FilteredType.php +++ b/seed/php-sdk/audiences/src/Foo/Types/FilteredType.php @@ -2,8 +2,8 @@ namespace Seed\Foo\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FilteredType extends SerializableType { diff --git a/seed/php-sdk/audiences/src/Foo/Types/ImportingType.php b/seed/php-sdk/audiences/src/Foo/Types/ImportingType.php index 199f8aecbbb..1091080c1e8 100644 --- a/seed/php-sdk/audiences/src/Foo/Types/ImportingType.php +++ b/seed/php-sdk/audiences/src/Foo/Types/ImportingType.php @@ -2,8 +2,8 @@ namespace Seed\Foo\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ImportingType extends SerializableType { diff --git a/seed/php-sdk/audiences/src/SeedClient.php b/seed/php-sdk/audiences/src/SeedClient.php index 83c694fdf0f..8fbf194fe7a 100644 --- a/seed/php-sdk/audiences/src/SeedClient.php +++ b/seed/php-sdk/audiences/src/SeedClient.php @@ -6,7 +6,7 @@ use Seed\FolderD\FolderDClient; use Seed\Foo\FooClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/audiences/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/EnumTest.php b/seed/php-sdk/audiences/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/audiences/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/audiences/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/audiences/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/audiences/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/audiences/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/audiences/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/audiences/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/ArrayType.php b/seed/php-sdk/auth-environment-variables/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/BaseApiRequest.php b/seed/php-sdk/auth-environment-variables/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/auth-environment-variables/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Client/HttpMethod.php b/seed/php-sdk/auth-environment-variables/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Constant.php b/seed/php-sdk/auth-environment-variables/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonDecoder.php b/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonEncoder.php b/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Json/SerializableType.php b/seed/php-sdk/auth-environment-variables/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Json/Utils.php b/seed/php-sdk/auth-environment-variables/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/JsonApiRequest.php b/seed/php-sdk/auth-environment-variables/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/JsonDecoder.php b/seed/php-sdk/auth-environment-variables/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/JsonDeserializer.php b/seed/php-sdk/auth-environment-variables/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/JsonEncoder.php b/seed/php-sdk/auth-environment-variables/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/RawClient.php b/seed/php-sdk/auth-environment-variables/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/SerializableType.php b/seed/php-sdk/auth-environment-variables/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Types/ArrayType.php b/seed/php-sdk/auth-environment-variables/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Types/Constant.php b/seed/php-sdk/auth-environment-variables/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Union.php b/seed/php-sdk/auth-environment-variables/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/Core/Utils.php b/seed/php-sdk/auth-environment-variables/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/auth-environment-variables/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/auth-environment-variables/src/SeedClient.php b/seed/php-sdk/auth-environment-variables/src/SeedClient.php index a1031a8b0aa..58d2a26dfc3 100644 --- a/seed/php-sdk/auth-environment-variables/src/SeedClient.php +++ b/seed/php-sdk/auth-environment-variables/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Exception; class SeedClient diff --git a/seed/php-sdk/auth-environment-variables/src/Service/Requests/HeaderAuthRequest.php b/seed/php-sdk/auth-environment-variables/src/Service/Requests/HeaderAuthRequest.php index b47a0e60ca9..5bed4fd60f2 100644 --- a/seed/php-sdk/auth-environment-variables/src/Service/Requests/HeaderAuthRequest.php +++ b/seed/php-sdk/auth-environment-variables/src/Service/Requests/HeaderAuthRequest.php @@ -2,7 +2,7 @@ namespace Seed\Service\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class HeaderAuthRequest extends SerializableType { diff --git a/seed/php-sdk/auth-environment-variables/src/Service/ServiceClient.php b/seed/php-sdk/auth-environment-variables/src/Service/ServiceClient.php index d1154985f67..5a7b7286f7d 100644 --- a/seed/php-sdk/auth-environment-variables/src/Service/ServiceClient.php +++ b/seed/php-sdk/auth-environment-variables/src/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Service\Requests\HeaderAuthRequest; diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/EnumTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/BasicAuth/BasicAuthClient.php b/seed/php-sdk/basic-auth-environment-variables/src/BasicAuth/BasicAuthClient.php index f528cd7cc36..a444ca1c93d 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/BasicAuth/BasicAuthClient.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/BasicAuth/BasicAuthClient.php @@ -2,12 +2,12 @@ namespace Seed\BasicAuth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/ArrayType.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/BaseApiRequest.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/HttpMethod.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Constant.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonDecoder.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonEncoder.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/SerializableType.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/Utils.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonApiRequest.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDecoder.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDeserializer.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonEncoder.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/RawClient.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/SerializableType.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Types/ArrayType.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Types/Constant.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Union.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Core/Utils.php b/seed/php-sdk/basic-auth-environment-variables/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/src/Errors/Types/UnauthorizedRequestErrorBody.php b/seed/php-sdk/basic-auth-environment-variables/src/Errors/Types/UnauthorizedRequestErrorBody.php index 2dfd23c1c41..df94820cb42 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/Errors/Types/UnauthorizedRequestErrorBody.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/Errors/Types/UnauthorizedRequestErrorBody.php @@ -2,8 +2,8 @@ namespace Seed\Errors\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UnauthorizedRequestErrorBody extends SerializableType { diff --git a/seed/php-sdk/basic-auth-environment-variables/src/SeedClient.php b/seed/php-sdk/basic-auth-environment-variables/src/SeedClient.php index 6d4ebd4c87d..85a2fb076b3 100644 --- a/seed/php-sdk/basic-auth-environment-variables/src/SeedClient.php +++ b/seed/php-sdk/basic-auth-environment-variables/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\BasicAuth\BasicAuthClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Exception; class SeedClient diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/EnumTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/basic-auth/src/BasicAuth/BasicAuthClient.php b/seed/php-sdk/basic-auth/src/BasicAuth/BasicAuthClient.php index f528cd7cc36..a444ca1c93d 100644 --- a/seed/php-sdk/basic-auth/src/BasicAuth/BasicAuthClient.php +++ b/seed/php-sdk/basic-auth/src/BasicAuth/BasicAuthClient.php @@ -2,12 +2,12 @@ namespace Seed\BasicAuth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/basic-auth/src/Core/ArrayType.php b/seed/php-sdk/basic-auth/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/BaseApiRequest.php b/seed/php-sdk/basic-auth/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/basic-auth/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Client/HttpMethod.php b/seed/php-sdk/basic-auth/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Constant.php b/seed/php-sdk/basic-auth/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Json/JsonDecoder.php b/seed/php-sdk/basic-auth/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/basic-auth/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Json/JsonEncoder.php b/seed/php-sdk/basic-auth/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Json/SerializableType.php b/seed/php-sdk/basic-auth/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Json/Utils.php b/seed/php-sdk/basic-auth/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/JsonApiRequest.php b/seed/php-sdk/basic-auth/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/JsonDecoder.php b/seed/php-sdk/basic-auth/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/JsonDeserializer.php b/seed/php-sdk/basic-auth/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/JsonEncoder.php b/seed/php-sdk/basic-auth/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/RawClient.php b/seed/php-sdk/basic-auth/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/SerializableType.php b/seed/php-sdk/basic-auth/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/Types/ArrayType.php b/seed/php-sdk/basic-auth/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Types/Constant.php b/seed/php-sdk/basic-auth/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/basic-auth/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/basic-auth/src/Core/Union.php b/seed/php-sdk/basic-auth/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/basic-auth/src/Core/Utils.php b/seed/php-sdk/basic-auth/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/basic-auth/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/basic-auth/src/Errors/Types/UnauthorizedRequestErrorBody.php b/seed/php-sdk/basic-auth/src/Errors/Types/UnauthorizedRequestErrorBody.php index 2dfd23c1c41..df94820cb42 100644 --- a/seed/php-sdk/basic-auth/src/Errors/Types/UnauthorizedRequestErrorBody.php +++ b/seed/php-sdk/basic-auth/src/Errors/Types/UnauthorizedRequestErrorBody.php @@ -2,8 +2,8 @@ namespace Seed\Errors\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UnauthorizedRequestErrorBody extends SerializableType { diff --git a/seed/php-sdk/basic-auth/src/SeedClient.php b/seed/php-sdk/basic-auth/src/SeedClient.php index 15b5458759c..dd5b49cce32 100644 --- a/seed/php-sdk/basic-auth/src/SeedClient.php +++ b/seed/php-sdk/basic-auth/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\BasicAuth\BasicAuthClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/EnumTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/basic-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/basic-auth/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/ArrayType.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/BaseApiRequest.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/HttpMethod.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Constant.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonDecoder.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonEncoder.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/SerializableType.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/Utils.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonApiRequest.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDecoder.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDeserializer.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonEncoder.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/RawClient.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/SerializableType.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Types/ArrayType.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Types/Constant.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Union.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Core/Utils.php b/seed/php-sdk/bearer-token-environment-variable/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/src/SeedClient.php b/seed/php-sdk/bearer-token-environment-variable/src/SeedClient.php index db3c8643e36..b555406fbb9 100644 --- a/seed/php-sdk/bearer-token-environment-variable/src/SeedClient.php +++ b/seed/php-sdk/bearer-token-environment-variable/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Exception; class SeedClient diff --git a/seed/php-sdk/bearer-token-environment-variable/src/Service/ServiceClient.php b/seed/php-sdk/bearer-token-environment-variable/src/Service/ServiceClient.php index 9125ff8851a..20f5cf42f75 100644 --- a/seed/php-sdk/bearer-token-environment-variable/src/Service/ServiceClient.php +++ b/seed/php-sdk/bearer-token-environment-variable/src/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/EnumTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/bytes/src/Core/ArrayType.php b/seed/php-sdk/bytes/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/bytes/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/bytes/src/Core/BaseApiRequest.php b/seed/php-sdk/bytes/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/bytes/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/bytes/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/bytes/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/bytes/src/Core/Client/HttpMethod.php b/seed/php-sdk/bytes/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/bytes/src/Core/Constant.php b/seed/php-sdk/bytes/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/bytes/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/bytes/src/Core/Json/JsonDecoder.php b/seed/php-sdk/bytes/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/bytes/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/bytes/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/bytes/src/Core/Json/JsonEncoder.php b/seed/php-sdk/bytes/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/bytes/src/Core/Json/SerializableType.php b/seed/php-sdk/bytes/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/bytes/src/Core/Json/Utils.php b/seed/php-sdk/bytes/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/bytes/src/Core/JsonApiRequest.php b/seed/php-sdk/bytes/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/bytes/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/bytes/src/Core/JsonDecoder.php b/seed/php-sdk/bytes/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/bytes/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/bytes/src/Core/JsonDeserializer.php b/seed/php-sdk/bytes/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/bytes/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/bytes/src/Core/JsonEncoder.php b/seed/php-sdk/bytes/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/bytes/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/bytes/src/Core/RawClient.php b/seed/php-sdk/bytes/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/bytes/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/bytes/src/Core/SerializableType.php b/seed/php-sdk/bytes/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/bytes/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/bytes/src/Core/Types/ArrayType.php b/seed/php-sdk/bytes/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/bytes/src/Core/Types/Constant.php b/seed/php-sdk/bytes/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/bytes/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/bytes/src/Core/Union.php b/seed/php-sdk/bytes/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/bytes/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/bytes/src/Core/Utils.php b/seed/php-sdk/bytes/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/bytes/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/bytes/src/SeedClient.php b/seed/php-sdk/bytes/src/SeedClient.php index 5f052555c44..4ca47b660f7 100644 --- a/seed/php-sdk/bytes/src/SeedClient.php +++ b/seed/php-sdk/bytes/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/bytes/src/Service/ServiceClient.php b/seed/php-sdk/bytes/src/Service/ServiceClient.php index 0c53cf41770..b2955d0e9e2 100644 --- a/seed/php-sdk/bytes/src/Service/ServiceClient.php +++ b/seed/php-sdk/bytes/src/Service/ServiceClient.php @@ -2,11 +2,11 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class ServiceClient diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/bytes/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/EnumTest.php b/seed/php-sdk/bytes/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/bytes/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/bytes/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/bytes/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/bytes/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/bytes/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/bytes/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/bytes/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/A/Types/A.php b/seed/php-sdk/circular-references-advanced/src/A/Types/A.php index 2d00e8ae915..5151cd85775 100644 --- a/seed/php-sdk/circular-references-advanced/src/A/Types/A.php +++ b/seed/php-sdk/circular-references-advanced/src/A/Types/A.php @@ -2,7 +2,7 @@ namespace Seed\A\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class A extends SerializableType { diff --git a/seed/php-sdk/circular-references-advanced/src/Ast/Types/ObjectFieldValue.php b/seed/php-sdk/circular-references-advanced/src/Ast/Types/ObjectFieldValue.php index a752da72618..24dfa161614 100644 --- a/seed/php-sdk/circular-references-advanced/src/Ast/Types/ObjectFieldValue.php +++ b/seed/php-sdk/circular-references-advanced/src/Ast/Types/ObjectFieldValue.php @@ -2,8 +2,8 @@ namespace Seed\Ast\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * This type allows us to test a circular reference with a union type (see FieldValue). diff --git a/seed/php-sdk/circular-references-advanced/src/Ast/Types/ObjectValue.php b/seed/php-sdk/circular-references-advanced/src/Ast/Types/ObjectValue.php index 2470003df8d..af94f61e471 100644 --- a/seed/php-sdk/circular-references-advanced/src/Ast/Types/ObjectValue.php +++ b/seed/php-sdk/circular-references-advanced/src/Ast/Types/ObjectValue.php @@ -2,7 +2,7 @@ namespace Seed\Ast\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ObjectValue extends SerializableType { diff --git a/seed/php-sdk/circular-references-advanced/src/Core/ArrayType.php b/seed/php-sdk/circular-references-advanced/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/BaseApiRequest.php b/seed/php-sdk/circular-references-advanced/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/circular-references-advanced/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Client/HttpMethod.php b/seed/php-sdk/circular-references-advanced/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Constant.php b/seed/php-sdk/circular-references-advanced/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonDecoder.php b/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonEncoder.php b/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Json/SerializableType.php b/seed/php-sdk/circular-references-advanced/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Json/Utils.php b/seed/php-sdk/circular-references-advanced/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/JsonApiRequest.php b/seed/php-sdk/circular-references-advanced/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/JsonDecoder.php b/seed/php-sdk/circular-references-advanced/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/JsonDeserializer.php b/seed/php-sdk/circular-references-advanced/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/JsonEncoder.php b/seed/php-sdk/circular-references-advanced/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/RawClient.php b/seed/php-sdk/circular-references-advanced/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/SerializableType.php b/seed/php-sdk/circular-references-advanced/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Types/ArrayType.php b/seed/php-sdk/circular-references-advanced/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Types/Constant.php b/seed/php-sdk/circular-references-advanced/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Union.php b/seed/php-sdk/circular-references-advanced/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Core/Utils.php b/seed/php-sdk/circular-references-advanced/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/circular-references-advanced/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/circular-references-advanced/src/Types/ImportingA.php b/seed/php-sdk/circular-references-advanced/src/Types/ImportingA.php index e828eeafc28..a66862c7509 100644 --- a/seed/php-sdk/circular-references-advanced/src/Types/ImportingA.php +++ b/seed/php-sdk/circular-references-advanced/src/Types/ImportingA.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\A\Types\A; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class ImportingA extends SerializableType { diff --git a/seed/php-sdk/circular-references-advanced/src/Types/RootType.php b/seed/php-sdk/circular-references-advanced/src/Types/RootType.php index 7ccfabd7bb7..49c3346132d 100644 --- a/seed/php-sdk/circular-references-advanced/src/Types/RootType.php +++ b/seed/php-sdk/circular-references-advanced/src/Types/RootType.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RootType extends SerializableType { diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/EnumTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/circular-references-advanced/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/circular-references/src/A/Types/A.php b/seed/php-sdk/circular-references/src/A/Types/A.php index 2d00e8ae915..5151cd85775 100644 --- a/seed/php-sdk/circular-references/src/A/Types/A.php +++ b/seed/php-sdk/circular-references/src/A/Types/A.php @@ -2,7 +2,7 @@ namespace Seed\A\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class A extends SerializableType { diff --git a/seed/php-sdk/circular-references/src/Ast/Types/ObjectValue.php b/seed/php-sdk/circular-references/src/Ast/Types/ObjectValue.php index 2470003df8d..af94f61e471 100644 --- a/seed/php-sdk/circular-references/src/Ast/Types/ObjectValue.php +++ b/seed/php-sdk/circular-references/src/Ast/Types/ObjectValue.php @@ -2,7 +2,7 @@ namespace Seed\Ast\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ObjectValue extends SerializableType { diff --git a/seed/php-sdk/circular-references/src/Core/ArrayType.php b/seed/php-sdk/circular-references/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/circular-references/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/circular-references/src/Core/BaseApiRequest.php b/seed/php-sdk/circular-references/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/circular-references/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/circular-references/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/circular-references/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Client/HttpMethod.php b/seed/php-sdk/circular-references/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Constant.php b/seed/php-sdk/circular-references/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/circular-references/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Json/JsonDecoder.php b/seed/php-sdk/circular-references/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/circular-references/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Json/JsonEncoder.php b/seed/php-sdk/circular-references/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Json/SerializableType.php b/seed/php-sdk/circular-references/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Json/Utils.php b/seed/php-sdk/circular-references/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/circular-references/src/Core/JsonApiRequest.php b/seed/php-sdk/circular-references/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/circular-references/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/circular-references/src/Core/JsonDecoder.php b/seed/php-sdk/circular-references/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/circular-references/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/circular-references/src/Core/JsonDeserializer.php b/seed/php-sdk/circular-references/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/circular-references/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/circular-references/src/Core/JsonEncoder.php b/seed/php-sdk/circular-references/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/circular-references/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/circular-references/src/Core/RawClient.php b/seed/php-sdk/circular-references/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/circular-references/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/circular-references/src/Core/SerializableType.php b/seed/php-sdk/circular-references/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/circular-references/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/circular-references/src/Core/Types/ArrayType.php b/seed/php-sdk/circular-references/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Types/Constant.php b/seed/php-sdk/circular-references/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/circular-references/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/circular-references/src/Core/Union.php b/seed/php-sdk/circular-references/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/circular-references/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/circular-references/src/Core/Utils.php b/seed/php-sdk/circular-references/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/circular-references/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/circular-references/src/Types/ImportingA.php b/seed/php-sdk/circular-references/src/Types/ImportingA.php index e828eeafc28..a66862c7509 100644 --- a/seed/php-sdk/circular-references/src/Types/ImportingA.php +++ b/seed/php-sdk/circular-references/src/Types/ImportingA.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\A\Types\A; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class ImportingA extends SerializableType { diff --git a/seed/php-sdk/circular-references/src/Types/RootType.php b/seed/php-sdk/circular-references/src/Types/RootType.php index 7ccfabd7bb7..49c3346132d 100644 --- a/seed/php-sdk/circular-references/src/Types/RootType.php +++ b/seed/php-sdk/circular-references/src/Types/RootType.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RootType extends SerializableType { diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/EnumTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/circular-references/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/circular-references/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/ArrayType.php b/seed/php-sdk/cross-package-type-names/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/BaseApiRequest.php b/seed/php-sdk/cross-package-type-names/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/cross-package-type-names/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Client/HttpMethod.php b/seed/php-sdk/cross-package-type-names/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Constant.php b/seed/php-sdk/cross-package-type-names/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonDecoder.php b/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonEncoder.php b/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Json/SerializableType.php b/seed/php-sdk/cross-package-type-names/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Json/Utils.php b/seed/php-sdk/cross-package-type-names/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/JsonApiRequest.php b/seed/php-sdk/cross-package-type-names/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/JsonDecoder.php b/seed/php-sdk/cross-package-type-names/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/JsonDeserializer.php b/seed/php-sdk/cross-package-type-names/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/JsonEncoder.php b/seed/php-sdk/cross-package-type-names/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/RawClient.php b/seed/php-sdk/cross-package-type-names/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/SerializableType.php b/seed/php-sdk/cross-package-type-names/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Types/ArrayType.php b/seed/php-sdk/cross-package-type-names/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Types/Constant.php b/seed/php-sdk/cross-package-type-names/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Union.php b/seed/php-sdk/cross-package-type-names/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/Core/Utils.php b/seed/php-sdk/cross-package-type-names/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/cross-package-type-names/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/cross-package-type-names/src/FolderA/FolderAClient.php b/seed/php-sdk/cross-package-type-names/src/FolderA/FolderAClient.php index e1124b6da8d..56b64f8553b 100644 --- a/seed/php-sdk/cross-package-type-names/src/FolderA/FolderAClient.php +++ b/seed/php-sdk/cross-package-type-names/src/FolderA/FolderAClient.php @@ -3,7 +3,7 @@ namespace Seed\FolderA; use Seed\FolderA\Service\ServiceClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class FolderAClient { diff --git a/seed/php-sdk/cross-package-type-names/src/FolderA/Service/ServiceClient.php b/seed/php-sdk/cross-package-type-names/src/FolderA/Service/ServiceClient.php index 9011e3e1ca9..c7c856fb630 100644 --- a/seed/php-sdk/cross-package-type-names/src/FolderA/Service/ServiceClient.php +++ b/seed/php-sdk/cross-package-type-names/src/FolderA/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\FolderA\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\FolderA\Service\Types\Response; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/cross-package-type-names/src/FolderA/Service/Types/Response.php b/seed/php-sdk/cross-package-type-names/src/FolderA/Service/Types/Response.php index af87a1efc2e..b9fa14d415c 100644 --- a/seed/php-sdk/cross-package-type-names/src/FolderA/Service/Types/Response.php +++ b/seed/php-sdk/cross-package-type-names/src/FolderA/Service/Types/Response.php @@ -2,9 +2,9 @@ namespace Seed\FolderA\Service\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderB\Common\Types\Foo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-sdk/cross-package-type-names/src/FolderB/Common/Types/Foo.php b/seed/php-sdk/cross-package-type-names/src/FolderB/Common/Types/Foo.php index 8789d47c668..41a9cce51d3 100644 --- a/seed/php-sdk/cross-package-type-names/src/FolderB/Common/Types/Foo.php +++ b/seed/php-sdk/cross-package-type-names/src/FolderB/Common/Types/Foo.php @@ -2,9 +2,9 @@ namespace Seed\FolderB\Common\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderC\Common\Types\Foo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Foo extends SerializableType { diff --git a/seed/php-sdk/cross-package-type-names/src/FolderC/Common/Types/Foo.php b/seed/php-sdk/cross-package-type-names/src/FolderC/Common/Types/Foo.php index 56347f456f3..584ee4db0f8 100644 --- a/seed/php-sdk/cross-package-type-names/src/FolderC/Common/Types/Foo.php +++ b/seed/php-sdk/cross-package-type-names/src/FolderC/Common/Types/Foo.php @@ -2,8 +2,8 @@ namespace Seed\FolderC\Common\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Foo extends SerializableType { diff --git a/seed/php-sdk/cross-package-type-names/src/FolderD/FolderDClient.php b/seed/php-sdk/cross-package-type-names/src/FolderD/FolderDClient.php index c8715e556d6..4d973ef9f72 100644 --- a/seed/php-sdk/cross-package-type-names/src/FolderD/FolderDClient.php +++ b/seed/php-sdk/cross-package-type-names/src/FolderD/FolderDClient.php @@ -3,7 +3,7 @@ namespace Seed\FolderD; use Seed\FolderD\Service\ServiceClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class FolderDClient { diff --git a/seed/php-sdk/cross-package-type-names/src/FolderD/Service/ServiceClient.php b/seed/php-sdk/cross-package-type-names/src/FolderD/Service/ServiceClient.php index aa016b3abe9..f91e7de5758 100644 --- a/seed/php-sdk/cross-package-type-names/src/FolderD/Service/ServiceClient.php +++ b/seed/php-sdk/cross-package-type-names/src/FolderD/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\FolderD\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\FolderD\Service\Types\Response; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/cross-package-type-names/src/FolderD/Service/Types/Response.php b/seed/php-sdk/cross-package-type-names/src/FolderD/Service/Types/Response.php index c50226737c0..dd906345927 100644 --- a/seed/php-sdk/cross-package-type-names/src/FolderD/Service/Types/Response.php +++ b/seed/php-sdk/cross-package-type-names/src/FolderD/Service/Types/Response.php @@ -2,9 +2,9 @@ namespace Seed\FolderD\Service\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\FolderB\Common\Types\Foo; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-sdk/cross-package-type-names/src/Foo/FooClient.php b/seed/php-sdk/cross-package-type-names/src/Foo/FooClient.php index 4af430995e6..98cb29b7679 100644 --- a/seed/php-sdk/cross-package-type-names/src/Foo/FooClient.php +++ b/seed/php-sdk/cross-package-type-names/src/Foo/FooClient.php @@ -2,13 +2,13 @@ namespace Seed\Foo; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Foo\Requests\FindRequest; use Seed\Foo\Types\ImportingType; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/cross-package-type-names/src/Foo/Requests/FindRequest.php b/seed/php-sdk/cross-package-type-names/src/Foo/Requests/FindRequest.php index e23f68a1a9a..5ca29173024 100644 --- a/seed/php-sdk/cross-package-type-names/src/Foo/Requests/FindRequest.php +++ b/seed/php-sdk/cross-package-type-names/src/Foo/Requests/FindRequest.php @@ -2,8 +2,8 @@ namespace Seed\Foo\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FindRequest extends SerializableType { diff --git a/seed/php-sdk/cross-package-type-names/src/Foo/Types/ImportingType.php b/seed/php-sdk/cross-package-type-names/src/Foo/Types/ImportingType.php index 199f8aecbbb..1091080c1e8 100644 --- a/seed/php-sdk/cross-package-type-names/src/Foo/Types/ImportingType.php +++ b/seed/php-sdk/cross-package-type-names/src/Foo/Types/ImportingType.php @@ -2,8 +2,8 @@ namespace Seed\Foo\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ImportingType extends SerializableType { diff --git a/seed/php-sdk/cross-package-type-names/src/SeedClient.php b/seed/php-sdk/cross-package-type-names/src/SeedClient.php index 83c694fdf0f..8fbf194fe7a 100644 --- a/seed/php-sdk/cross-package-type-names/src/SeedClient.php +++ b/seed/php-sdk/cross-package-type-names/src/SeedClient.php @@ -6,7 +6,7 @@ use Seed\FolderD\FolderDClient; use Seed\Foo\FooClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/EnumTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/cross-package-type-names/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/ArrayType.php b/seed/php-sdk/custom-auth/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/BaseApiRequest.php b/seed/php-sdk/custom-auth/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/custom-auth/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Client/HttpMethod.php b/seed/php-sdk/custom-auth/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Constant.php b/seed/php-sdk/custom-auth/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Json/JsonDecoder.php b/seed/php-sdk/custom-auth/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/custom-auth/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Json/JsonEncoder.php b/seed/php-sdk/custom-auth/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Json/SerializableType.php b/seed/php-sdk/custom-auth/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Json/Utils.php b/seed/php-sdk/custom-auth/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/JsonApiRequest.php b/seed/php-sdk/custom-auth/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/JsonDecoder.php b/seed/php-sdk/custom-auth/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/JsonDeserializer.php b/seed/php-sdk/custom-auth/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/JsonEncoder.php b/seed/php-sdk/custom-auth/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/RawClient.php b/seed/php-sdk/custom-auth/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/SerializableType.php b/seed/php-sdk/custom-auth/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/Types/ArrayType.php b/seed/php-sdk/custom-auth/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Types/Constant.php b/seed/php-sdk/custom-auth/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/custom-auth/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/custom-auth/src/Core/Union.php b/seed/php-sdk/custom-auth/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/custom-auth/src/Core/Utils.php b/seed/php-sdk/custom-auth/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/custom-auth/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/custom-auth/src/CustomAuth/CustomAuthClient.php b/seed/php-sdk/custom-auth/src/CustomAuth/CustomAuthClient.php index a0c8bfc305d..8ecf0df42b9 100644 --- a/seed/php-sdk/custom-auth/src/CustomAuth/CustomAuthClient.php +++ b/seed/php-sdk/custom-auth/src/CustomAuth/CustomAuthClient.php @@ -2,12 +2,12 @@ namespace Seed\CustomAuth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/custom-auth/src/Errors/Types/UnauthorizedRequestErrorBody.php b/seed/php-sdk/custom-auth/src/Errors/Types/UnauthorizedRequestErrorBody.php index 2dfd23c1c41..df94820cb42 100644 --- a/seed/php-sdk/custom-auth/src/Errors/Types/UnauthorizedRequestErrorBody.php +++ b/seed/php-sdk/custom-auth/src/Errors/Types/UnauthorizedRequestErrorBody.php @@ -2,8 +2,8 @@ namespace Seed\Errors\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UnauthorizedRequestErrorBody extends SerializableType { diff --git a/seed/php-sdk/custom-auth/src/SeedClient.php b/seed/php-sdk/custom-auth/src/SeedClient.php index aa034bfd172..db9bda3e283 100644 --- a/seed/php-sdk/custom-auth/src/SeedClient.php +++ b/seed/php-sdk/custom-auth/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\CustomAuth\CustomAuthClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/EnumTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/custom-auth/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/custom-auth/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/enum/src/Core/ArrayType.php b/seed/php-sdk/enum/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/enum/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/enum/src/Core/BaseApiRequest.php b/seed/php-sdk/enum/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/enum/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/enum/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/enum/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/enum/src/Core/Client/HttpMethod.php b/seed/php-sdk/enum/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/enum/src/Core/Constant.php b/seed/php-sdk/enum/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/enum/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/enum/src/Core/Json/JsonDecoder.php b/seed/php-sdk/enum/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/enum/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/enum/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/enum/src/Core/Json/JsonEncoder.php b/seed/php-sdk/enum/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/enum/src/Core/Json/SerializableType.php b/seed/php-sdk/enum/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/enum/src/Core/Json/Utils.php b/seed/php-sdk/enum/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/enum/src/Core/JsonApiRequest.php b/seed/php-sdk/enum/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/enum/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/enum/src/Core/JsonDecoder.php b/seed/php-sdk/enum/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/enum/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/enum/src/Core/JsonDeserializer.php b/seed/php-sdk/enum/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/enum/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/enum/src/Core/JsonEncoder.php b/seed/php-sdk/enum/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/enum/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/enum/src/Core/RawClient.php b/seed/php-sdk/enum/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/enum/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/enum/src/Core/SerializableType.php b/seed/php-sdk/enum/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/enum/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/enum/src/Core/Types/ArrayType.php b/seed/php-sdk/enum/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/enum/src/Core/Types/Constant.php b/seed/php-sdk/enum/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/enum/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/enum/src/Core/Union.php b/seed/php-sdk/enum/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/enum/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/enum/src/Core/Utils.php b/seed/php-sdk/enum/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/enum/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/enum/src/InlinedRequest/InlinedRequestClient.php b/seed/php-sdk/enum/src/InlinedRequest/InlinedRequestClient.php index 99eafa32dc8..e34fcd9f6e3 100644 --- a/seed/php-sdk/enum/src/InlinedRequest/InlinedRequestClient.php +++ b/seed/php-sdk/enum/src/InlinedRequest/InlinedRequestClient.php @@ -2,12 +2,12 @@ namespace Seed\InlinedRequest; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\InlinedRequest\Requests\SendEnumInlinedRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class InlinedRequestClient diff --git a/seed/php-sdk/enum/src/InlinedRequest/Requests/SendEnumInlinedRequest.php b/seed/php-sdk/enum/src/InlinedRequest/Requests/SendEnumInlinedRequest.php index eca5e93593c..65bb018b573 100644 --- a/seed/php-sdk/enum/src/InlinedRequest/Requests/SendEnumInlinedRequest.php +++ b/seed/php-sdk/enum/src/InlinedRequest/Requests/SendEnumInlinedRequest.php @@ -2,9 +2,9 @@ namespace Seed\InlinedRequest\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Types\Operand; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; use Seed\Types\Color; class SendEnumInlinedRequest extends SerializableType diff --git a/seed/php-sdk/enum/src/PathParam/PathParamClient.php b/seed/php-sdk/enum/src/PathParam/PathParamClient.php index 99bacb44266..eafd38cfd05 100644 --- a/seed/php-sdk/enum/src/PathParam/PathParamClient.php +++ b/seed/php-sdk/enum/src/PathParam/PathParamClient.php @@ -2,13 +2,13 @@ namespace Seed\PathParam; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Types\Operand; use Seed\Types\Color; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class PathParamClient diff --git a/seed/php-sdk/enum/src/QueryParam/QueryParamClient.php b/seed/php-sdk/enum/src/QueryParam/QueryParamClient.php index 62a889a9e3f..b68c79ab502 100644 --- a/seed/php-sdk/enum/src/QueryParam/QueryParamClient.php +++ b/seed/php-sdk/enum/src/QueryParam/QueryParamClient.php @@ -2,12 +2,12 @@ namespace Seed\QueryParam; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\QueryParam\Requests\SendEnumAsQueryParamRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; use Seed\QueryParam\Requests\SendEnumListAsQueryParamRequest; diff --git a/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumAsQueryParamRequest.php b/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumAsQueryParamRequest.php index d73c5650fb0..e8af8844276 100644 --- a/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumAsQueryParamRequest.php +++ b/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumAsQueryParamRequest.php @@ -2,7 +2,7 @@ namespace Seed\QueryParam\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Types\Operand; use Seed\Types\Color; diff --git a/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumListAsQueryParamRequest.php b/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumListAsQueryParamRequest.php index b7c0a49cde0..f762f6fa41e 100644 --- a/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumListAsQueryParamRequest.php +++ b/seed/php-sdk/enum/src/QueryParam/Requests/SendEnumListAsQueryParamRequest.php @@ -2,7 +2,7 @@ namespace Seed\QueryParam\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Types\Operand; use Seed\Types\Color; diff --git a/seed/php-sdk/enum/src/SeedClient.php b/seed/php-sdk/enum/src/SeedClient.php index 5f790591647..32fd7586ee1 100644 --- a/seed/php-sdk/enum/src/SeedClient.php +++ b/seed/php-sdk/enum/src/SeedClient.php @@ -6,7 +6,7 @@ use Seed\PathParam\PathParamClient; use Seed\QueryParam\QueryParamClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/enum/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/enum/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/enum/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/EnumTest.php b/seed/php-sdk/enum/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/enum/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/enum/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/enum/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/enum/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/enum/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/enum/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/enum/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/enum/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/error-property/src/Core/ArrayType.php b/seed/php-sdk/error-property/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/error-property/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/error-property/src/Core/BaseApiRequest.php b/seed/php-sdk/error-property/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/error-property/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/error-property/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/error-property/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/error-property/src/Core/Client/HttpMethod.php b/seed/php-sdk/error-property/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/error-property/src/Core/Constant.php b/seed/php-sdk/error-property/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/error-property/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/error-property/src/Core/Json/JsonDecoder.php b/seed/php-sdk/error-property/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/error-property/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/error-property/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/error-property/src/Core/Json/JsonEncoder.php b/seed/php-sdk/error-property/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/error-property/src/Core/Json/SerializableType.php b/seed/php-sdk/error-property/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/error-property/src/Core/Json/Utils.php b/seed/php-sdk/error-property/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/error-property/src/Core/JsonApiRequest.php b/seed/php-sdk/error-property/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/error-property/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/error-property/src/Core/JsonDecoder.php b/seed/php-sdk/error-property/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/error-property/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/error-property/src/Core/JsonDeserializer.php b/seed/php-sdk/error-property/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/error-property/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/error-property/src/Core/JsonEncoder.php b/seed/php-sdk/error-property/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/error-property/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/error-property/src/Core/RawClient.php b/seed/php-sdk/error-property/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/error-property/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/error-property/src/Core/SerializableType.php b/seed/php-sdk/error-property/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/error-property/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/error-property/src/Core/Types/ArrayType.php b/seed/php-sdk/error-property/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/error-property/src/Core/Types/Constant.php b/seed/php-sdk/error-property/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/error-property/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/error-property/src/Core/Union.php b/seed/php-sdk/error-property/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/error-property/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/error-property/src/Core/Utils.php b/seed/php-sdk/error-property/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/error-property/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/error-property/src/Errors/Types/PropertyBasedErrorTestBody.php b/seed/php-sdk/error-property/src/Errors/Types/PropertyBasedErrorTestBody.php index ce196f82c2d..a3158a83d78 100644 --- a/seed/php-sdk/error-property/src/Errors/Types/PropertyBasedErrorTestBody.php +++ b/seed/php-sdk/error-property/src/Errors/Types/PropertyBasedErrorTestBody.php @@ -2,8 +2,8 @@ namespace Seed\Errors\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class PropertyBasedErrorTestBody extends SerializableType { diff --git a/seed/php-sdk/error-property/src/PropertyBasedError/PropertyBasedErrorClient.php b/seed/php-sdk/error-property/src/PropertyBasedError/PropertyBasedErrorClient.php index 32463ce3c86..6432f9a3caf 100644 --- a/seed/php-sdk/error-property/src/PropertyBasedError/PropertyBasedErrorClient.php +++ b/seed/php-sdk/error-property/src/PropertyBasedError/PropertyBasedErrorClient.php @@ -2,12 +2,12 @@ namespace Seed\PropertyBasedError; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/error-property/src/SeedClient.php b/seed/php-sdk/error-property/src/SeedClient.php index 22fbea6c6f0..f45e7b60678 100644 --- a/seed/php-sdk/error-property/src/SeedClient.php +++ b/seed/php-sdk/error-property/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\PropertyBasedError\PropertyBasedErrorClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/error-property/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/EnumTest.php b/seed/php-sdk/error-property/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/error-property/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/error-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/error-property/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/error-property/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/error-property/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/error-property/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/error-property/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/examples/src/Commons/Types/Types/Metadata.php b/seed/php-sdk/examples/src/Commons/Types/Types/Metadata.php index 7c4be407422..eac7d042076 100644 --- a/seed/php-sdk/examples/src/Commons/Types/Types/Metadata.php +++ b/seed/php-sdk/examples/src/Commons/Types/Types/Metadata.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Metadata extends SerializableType { diff --git a/seed/php-sdk/examples/src/Core/ArrayType.php b/seed/php-sdk/examples/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/examples/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/examples/src/Core/BaseApiRequest.php b/seed/php-sdk/examples/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/examples/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/examples/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/examples/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/examples/src/Core/Client/HttpMethod.php b/seed/php-sdk/examples/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/examples/src/Core/Constant.php b/seed/php-sdk/examples/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/examples/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/examples/src/Core/Json/JsonDecoder.php b/seed/php-sdk/examples/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/examples/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/examples/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/examples/src/Core/Json/JsonEncoder.php b/seed/php-sdk/examples/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/examples/src/Core/Json/SerializableType.php b/seed/php-sdk/examples/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/examples/src/Core/Json/Utils.php b/seed/php-sdk/examples/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/examples/src/Core/JsonApiRequest.php b/seed/php-sdk/examples/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/examples/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/examples/src/Core/JsonDecoder.php b/seed/php-sdk/examples/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/examples/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/examples/src/Core/JsonDeserializer.php b/seed/php-sdk/examples/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/examples/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/examples/src/Core/JsonEncoder.php b/seed/php-sdk/examples/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/examples/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/examples/src/Core/RawClient.php b/seed/php-sdk/examples/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/examples/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/examples/src/Core/SerializableType.php b/seed/php-sdk/examples/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/examples/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/examples/src/Core/Types/ArrayType.php b/seed/php-sdk/examples/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/examples/src/Core/Types/Constant.php b/seed/php-sdk/examples/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/examples/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/examples/src/Core/Union.php b/seed/php-sdk/examples/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/examples/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/examples/src/Core/Utils.php b/seed/php-sdk/examples/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/examples/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/examples/src/File/FileClient.php b/seed/php-sdk/examples/src/File/FileClient.php index 5bb24bd3464..d59de7c74d8 100644 --- a/seed/php-sdk/examples/src/File/FileClient.php +++ b/seed/php-sdk/examples/src/File/FileClient.php @@ -4,7 +4,7 @@ use Seed\File\Notification\NotificationClient; use Seed\File\Service\ServiceClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class FileClient { diff --git a/seed/php-sdk/examples/src/File/Notification/NotificationClient.php b/seed/php-sdk/examples/src/File/Notification/NotificationClient.php index 7b4b9c7ab44..6563eb6df29 100644 --- a/seed/php-sdk/examples/src/File/Notification/NotificationClient.php +++ b/seed/php-sdk/examples/src/File/Notification/NotificationClient.php @@ -3,7 +3,7 @@ namespace Seed\File\Notification; use Seed\File\Notification\Service\ServiceClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class NotificationClient { diff --git a/seed/php-sdk/examples/src/File/Notification/Service/ServiceClient.php b/seed/php-sdk/examples/src/File/Notification/Service/ServiceClient.php index b98c01f65bd..bafa2763ef8 100644 --- a/seed/php-sdk/examples/src/File/Notification/Service/ServiceClient.php +++ b/seed/php-sdk/examples/src/File/Notification/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\File\Notification\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/examples/src/File/Service/Requests/GetFileRequest.php b/seed/php-sdk/examples/src/File/Service/Requests/GetFileRequest.php index 13e9ffbea5f..c655c09fb85 100644 --- a/seed/php-sdk/examples/src/File/Service/Requests/GetFileRequest.php +++ b/seed/php-sdk/examples/src/File/Service/Requests/GetFileRequest.php @@ -2,7 +2,7 @@ namespace Seed\File\Service\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetFileRequest extends SerializableType { diff --git a/seed/php-sdk/examples/src/File/Service/ServiceClient.php b/seed/php-sdk/examples/src/File/Service/ServiceClient.php index 2f3daca8e97..bac38864ef3 100644 --- a/seed/php-sdk/examples/src/File/Service/ServiceClient.php +++ b/seed/php-sdk/examples/src/File/Service/ServiceClient.php @@ -2,13 +2,13 @@ namespace Seed\File\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\File\Service\Requests\GetFileRequest; use Seed\Types\Types\File; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/examples/src/Health/HealthClient.php b/seed/php-sdk/examples/src/Health/HealthClient.php index 1959d2c4e7d..9527642d15e 100644 --- a/seed/php-sdk/examples/src/Health/HealthClient.php +++ b/seed/php-sdk/examples/src/Health/HealthClient.php @@ -3,7 +3,7 @@ namespace Seed\Health; use Seed\Health\Service\ServiceClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class HealthClient { diff --git a/seed/php-sdk/examples/src/Health/Service/ServiceClient.php b/seed/php-sdk/examples/src/Health/Service/ServiceClient.php index ddabd8782f8..daa4ae07159 100644 --- a/seed/php-sdk/examples/src/Health/Service/ServiceClient.php +++ b/seed/php-sdk/examples/src/Health/Service/ServiceClient.php @@ -2,13 +2,13 @@ namespace Seed\Health\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonDecoder; use JsonException; class ServiceClient diff --git a/seed/php-sdk/examples/src/SeedClient.php b/seed/php-sdk/examples/src/SeedClient.php index 73c2eccc876..2c5e92f15c5 100644 --- a/seed/php-sdk/examples/src/SeedClient.php +++ b/seed/php-sdk/examples/src/SeedClient.php @@ -6,12 +6,12 @@ use Seed\Health\HealthClient; use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/examples/src/Service/Requests/GetMetadataRequest.php b/seed/php-sdk/examples/src/Service/Requests/GetMetadataRequest.php index d59db3583f4..e3d75f364f0 100644 --- a/seed/php-sdk/examples/src/Service/Requests/GetMetadataRequest.php +++ b/seed/php-sdk/examples/src/Service/Requests/GetMetadataRequest.php @@ -2,7 +2,7 @@ namespace Seed\Service\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetMetadataRequest extends SerializableType { diff --git a/seed/php-sdk/examples/src/Service/ServiceClient.php b/seed/php-sdk/examples/src/Service/ServiceClient.php index e9762d6ce05..b522376dcb3 100644 --- a/seed/php-sdk/examples/src/Service/ServiceClient.php +++ b/seed/php-sdk/examples/src/Service/ServiceClient.php @@ -2,15 +2,15 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Types\Types\Movie; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonDecoder; use Seed\Service\Requests\GetMetadataRequest; use Seed\Types\Types\Response; diff --git a/seed/php-sdk/examples/src/Types/Identifier.php b/seed/php-sdk/examples/src/Types/Identifier.php index efcb8cc8274..c2906545cd4 100644 --- a/seed/php-sdk/examples/src/Types/Identifier.php +++ b/seed/php-sdk/examples/src/Types/Identifier.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Identifier extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Actor.php b/seed/php-sdk/examples/src/Types/Types/Actor.php index bfb5e78d445..a1cc7d61c85 100644 --- a/seed/php-sdk/examples/src/Types/Types/Actor.php +++ b/seed/php-sdk/examples/src/Types/Types/Actor.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Actor extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Actress.php b/seed/php-sdk/examples/src/Types/Types/Actress.php index 6ffd1c98001..1a920e121c4 100644 --- a/seed/php-sdk/examples/src/Types/Types/Actress.php +++ b/seed/php-sdk/examples/src/Types/Types/Actress.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Actress extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Directory.php b/seed/php-sdk/examples/src/Types/Types/Directory.php index 3c380f60272..f8c1a4ef2f9 100644 --- a/seed/php-sdk/examples/src/Types/Types/Directory.php +++ b/seed/php-sdk/examples/src/Types/Types/Directory.php @@ -2,9 +2,9 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Directory extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Entity.php b/seed/php-sdk/examples/src/Types/Types/Entity.php index 830aef6b2f4..61e14f5070b 100644 --- a/seed/php-sdk/examples/src/Types/Types/Entity.php +++ b/seed/php-sdk/examples/src/Types/Types/Entity.php @@ -2,10 +2,10 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Types\BasicType; use Seed\Types\ComplexType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class Entity extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/ExceptionInfo.php b/seed/php-sdk/examples/src/Types/Types/ExceptionInfo.php index 780ef2c1c7c..9b54acd1fb8 100644 --- a/seed/php-sdk/examples/src/Types/Types/ExceptionInfo.php +++ b/seed/php-sdk/examples/src/Types/Types/ExceptionInfo.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExceptionInfo extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/ExtendedMovie.php b/seed/php-sdk/examples/src/Types/Types/ExtendedMovie.php index 201a4bca303..53833254891 100644 --- a/seed/php-sdk/examples/src/Types/Types/ExtendedMovie.php +++ b/seed/php-sdk/examples/src/Types/Types/ExtendedMovie.php @@ -2,9 +2,9 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ExtendedMovie extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/File.php b/seed/php-sdk/examples/src/Types/Types/File.php index b5076e66119..51fb139df65 100644 --- a/seed/php-sdk/examples/src/Types/Types/File.php +++ b/seed/php-sdk/examples/src/Types/Types/File.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class File extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Migration.php b/seed/php-sdk/examples/src/Types/Types/Migration.php index 0a498af7e43..c3ec0f74020 100644 --- a/seed/php-sdk/examples/src/Types/Types/Migration.php +++ b/seed/php-sdk/examples/src/Types/Types/Migration.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Migration extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Moment.php b/seed/php-sdk/examples/src/Types/Types/Moment.php index deec094e388..6b8e74e8455 100644 --- a/seed/php-sdk/examples/src/Types/Types/Moment.php +++ b/seed/php-sdk/examples/src/Types/Types/Moment.php @@ -2,10 +2,10 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use DateTime; -use Seed\Core\DateType; +use Seed\Core\Types\DateType; class Moment extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Movie.php b/seed/php-sdk/examples/src/Types/Types/Movie.php index 8a97108dea2..41eed4db4da 100644 --- a/seed/php-sdk/examples/src/Types/Types/Movie.php +++ b/seed/php-sdk/examples/src/Types/Types/Movie.php @@ -2,9 +2,9 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Movie extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Node.php b/seed/php-sdk/examples/src/Types/Types/Node.php index 055b9357e7a..5f6efabc4d2 100644 --- a/seed/php-sdk/examples/src/Types/Types/Node.php +++ b/seed/php-sdk/examples/src/Types/Types/Node.php @@ -2,9 +2,9 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Node extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Request.php b/seed/php-sdk/examples/src/Types/Types/Request.php index fbcc6e3b2b9..d7550ef93f0 100644 --- a/seed/php-sdk/examples/src/Types/Types/Request.php +++ b/seed/php-sdk/examples/src/Types/Types/Request.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Request extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Response.php b/seed/php-sdk/examples/src/Types/Types/Response.php index 2a38024ce3c..d9ef182fbc2 100644 --- a/seed/php-sdk/examples/src/Types/Types/Response.php +++ b/seed/php-sdk/examples/src/Types/Types/Response.php @@ -2,10 +2,10 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Types\Identifier; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class Response extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/ResponseType.php b/seed/php-sdk/examples/src/Types/Types/ResponseType.php index 71faed3ceea..e365fad4f51 100644 --- a/seed/php-sdk/examples/src/Types/Types/ResponseType.php +++ b/seed/php-sdk/examples/src/Types/Types/ResponseType.php @@ -2,10 +2,10 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Types\BasicType; use Seed\Types\ComplexType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class ResponseType extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/StuntDouble.php b/seed/php-sdk/examples/src/Types/Types/StuntDouble.php index 57461a97470..3bffd68abce 100644 --- a/seed/php-sdk/examples/src/Types/Types/StuntDouble.php +++ b/seed/php-sdk/examples/src/Types/Types/StuntDouble.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StuntDouble extends SerializableType { diff --git a/seed/php-sdk/examples/src/Types/Types/Tree.php b/seed/php-sdk/examples/src/Types/Types/Tree.php index dfe3b11be8d..016036fe1c0 100644 --- a/seed/php-sdk/examples/src/Types/Types/Tree.php +++ b/seed/php-sdk/examples/src/Types/Types/Tree.php @@ -2,9 +2,9 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Tree extends SerializableType { diff --git a/seed/php-sdk/examples/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/examples/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/examples/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/EnumTest.php b/seed/php-sdk/examples/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/examples/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/examples/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/examples/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/examples/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/examples/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/examples/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/examples/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/ArrayType.php b/seed/php-sdk/exhaustive/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/BaseApiRequest.php b/seed/php-sdk/exhaustive/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/exhaustive/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Client/HttpMethod.php b/seed/php-sdk/exhaustive/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Constant.php b/seed/php-sdk/exhaustive/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Json/JsonDecoder.php b/seed/php-sdk/exhaustive/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/exhaustive/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Json/JsonEncoder.php b/seed/php-sdk/exhaustive/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Json/SerializableType.php b/seed/php-sdk/exhaustive/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Json/Utils.php b/seed/php-sdk/exhaustive/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/JsonApiRequest.php b/seed/php-sdk/exhaustive/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/JsonDecoder.php b/seed/php-sdk/exhaustive/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/JsonDeserializer.php b/seed/php-sdk/exhaustive/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/JsonEncoder.php b/seed/php-sdk/exhaustive/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/RawClient.php b/seed/php-sdk/exhaustive/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/SerializableType.php b/seed/php-sdk/exhaustive/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/Types/ArrayType.php b/seed/php-sdk/exhaustive/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Types/Constant.php b/seed/php-sdk/exhaustive/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/exhaustive/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/exhaustive/src/Core/Union.php b/seed/php-sdk/exhaustive/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/exhaustive/src/Core/Utils.php b/seed/php-sdk/exhaustive/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/exhaustive/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Container/ContainerClient.php b/seed/php-sdk/exhaustive/src/Endpoints/Container/ContainerClient.php index 49a1d48058b..97469cb19fe 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Container/ContainerClient.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Container/ContainerClient.php @@ -2,13 +2,13 @@ namespace Seed\Endpoints\Container; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonSerializer; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonSerializer; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Types\Object\Types\ObjectWithRequiredField; diff --git a/seed/php-sdk/exhaustive/src/Endpoints/EndpointsClient.php b/seed/php-sdk/exhaustive/src/Endpoints/EndpointsClient.php index f90e4a96dee..1fca36a88ae 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/EndpointsClient.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/EndpointsClient.php @@ -9,7 +9,7 @@ use Seed\Endpoints\Params\ParamsClient; use Seed\Endpoints\Primitive\PrimitiveClient; use Seed\Endpoints\Union\UnionClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class EndpointsClient { diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Enum/EnumClient.php b/seed/php-sdk/exhaustive/src/Endpoints/Enum/EnumClient.php index 31a3a6d82a2..0d349d3d1cd 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Enum/EnumClient.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Enum/EnumClient.php @@ -2,13 +2,13 @@ namespace Seed\Endpoints\Enum; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Types\Enum\Types\WeatherReport; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/exhaustive/src/Endpoints/HttpMethods/HttpMethodsClient.php b/seed/php-sdk/exhaustive/src/Endpoints/HttpMethods/HttpMethodsClient.php index 9560aa849d6..ade96b553f5 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/HttpMethods/HttpMethodsClient.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/HttpMethods/HttpMethodsClient.php @@ -2,12 +2,12 @@ namespace Seed\Endpoints\HttpMethods; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Types\Object\Types\ObjectWithRequiredField; diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Object/ObjectClient.php b/seed/php-sdk/exhaustive/src/Endpoints/Object/ObjectClient.php index a382e2c7194..a990dad8034 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Object/ObjectClient.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Object/ObjectClient.php @@ -2,19 +2,19 @@ namespace Seed\Endpoints\Object; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Types\Object\Types\ObjectWithOptionalField; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Types\Object\Types\ObjectWithRequiredField; use Seed\Types\Object\Types\ObjectWithMapOfMap; use Seed\Types\Object\Types\NestedObjectWithOptionalField; use Seed\Types\Object\Types\NestedObjectWithRequiredField; -use Seed\Core\JsonSerializer; +use Seed\Core\Json\JsonSerializer; class ObjectClient { diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Params/ParamsClient.php b/seed/php-sdk/exhaustive/src/Endpoints/Params/ParamsClient.php index 3d874ccd1fa..ede6c77ba3d 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Params/ParamsClient.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Params/ParamsClient.php @@ -2,12 +2,12 @@ namespace Seed\Endpoints\Params; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Endpoints\Params\Requests\GetWithQuery; diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithMultipleQuery.php b/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithMultipleQuery.php index 1e630ac1ff0..88d878db323 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithMultipleQuery.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithMultipleQuery.php @@ -2,7 +2,7 @@ namespace Seed\Endpoints\Params\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetWithMultipleQuery extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithPathAndQuery.php b/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithPathAndQuery.php index e1b716d4ca9..63236c2fd66 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithPathAndQuery.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithPathAndQuery.php @@ -2,7 +2,7 @@ namespace Seed\Endpoints\Params\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetWithPathAndQuery extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithQuery.php b/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithQuery.php index 816e0ec875f..2b5391d571b 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithQuery.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Params/Requests/GetWithQuery.php @@ -2,7 +2,7 @@ namespace Seed\Endpoints\Params\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetWithQuery extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Primitive/PrimitiveClient.php b/seed/php-sdk/exhaustive/src/Endpoints/Primitive/PrimitiveClient.php index cd1aa97a121..1eae4a42961 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Primitive/PrimitiveClient.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Primitive/PrimitiveClient.php @@ -2,16 +2,16 @@ namespace Seed\Endpoints\Primitive; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use DateTime; -use Seed\Core\JsonSerializer; +use Seed\Core\Json\JsonSerializer; class PrimitiveClient { diff --git a/seed/php-sdk/exhaustive/src/Endpoints/Union/UnionClient.php b/seed/php-sdk/exhaustive/src/Endpoints/Union/UnionClient.php index dd6ab47a1af..4950ec84aca 100644 --- a/seed/php-sdk/exhaustive/src/Endpoints/Union/UnionClient.php +++ b/seed/php-sdk/exhaustive/src/Endpoints/Union/UnionClient.php @@ -2,12 +2,12 @@ namespace Seed\Endpoints\Union; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/exhaustive/src/GeneralErrors/Types/BadObjectRequestInfo.php b/seed/php-sdk/exhaustive/src/GeneralErrors/Types/BadObjectRequestInfo.php index d9ab21275d0..0bd0cba11ae 100644 --- a/seed/php-sdk/exhaustive/src/GeneralErrors/Types/BadObjectRequestInfo.php +++ b/seed/php-sdk/exhaustive/src/GeneralErrors/Types/BadObjectRequestInfo.php @@ -2,8 +2,8 @@ namespace Seed\GeneralErrors\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BadObjectRequestInfo extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/InlinedRequests/InlinedRequestsClient.php b/seed/php-sdk/exhaustive/src/InlinedRequests/InlinedRequestsClient.php index 95d2b63e9b2..658b04888c8 100644 --- a/seed/php-sdk/exhaustive/src/InlinedRequests/InlinedRequestsClient.php +++ b/seed/php-sdk/exhaustive/src/InlinedRequests/InlinedRequestsClient.php @@ -2,13 +2,13 @@ namespace Seed\InlinedRequests; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\InlinedRequests\Requests\PostWithObjectBody; use Seed\Types\Object\Types\ObjectWithOptionalField; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/exhaustive/src/InlinedRequests/Requests/PostWithObjectBody.php b/seed/php-sdk/exhaustive/src/InlinedRequests/Requests/PostWithObjectBody.php index 3549be70c93..6a2a3f3e1f2 100644 --- a/seed/php-sdk/exhaustive/src/InlinedRequests/Requests/PostWithObjectBody.php +++ b/seed/php-sdk/exhaustive/src/InlinedRequests/Requests/PostWithObjectBody.php @@ -2,8 +2,8 @@ namespace Seed\InlinedRequests\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Types\Object\Types\ObjectWithOptionalField; class PostWithObjectBody extends SerializableType diff --git a/seed/php-sdk/exhaustive/src/NoAuth/NoAuthClient.php b/seed/php-sdk/exhaustive/src/NoAuth/NoAuthClient.php index 72397667977..3093d0b019b 100644 --- a/seed/php-sdk/exhaustive/src/NoAuth/NoAuthClient.php +++ b/seed/php-sdk/exhaustive/src/NoAuth/NoAuthClient.php @@ -2,12 +2,12 @@ namespace Seed\NoAuth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/exhaustive/src/NoReqBody/NoReqBodyClient.php b/seed/php-sdk/exhaustive/src/NoReqBody/NoReqBodyClient.php index d8ca869aa83..0fb93b395d5 100644 --- a/seed/php-sdk/exhaustive/src/NoReqBody/NoReqBodyClient.php +++ b/seed/php-sdk/exhaustive/src/NoReqBody/NoReqBodyClient.php @@ -2,15 +2,15 @@ namespace Seed\NoReqBody; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Types\Object\Types\ObjectWithOptionalField; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonDecoder; class NoReqBodyClient { diff --git a/seed/php-sdk/exhaustive/src/ReqWithHeaders/ReqWithHeadersClient.php b/seed/php-sdk/exhaustive/src/ReqWithHeaders/ReqWithHeadersClient.php index 82f7d2aa36c..3cddf2c0e9a 100644 --- a/seed/php-sdk/exhaustive/src/ReqWithHeaders/ReqWithHeadersClient.php +++ b/seed/php-sdk/exhaustive/src/ReqWithHeaders/ReqWithHeadersClient.php @@ -2,12 +2,12 @@ namespace Seed\ReqWithHeaders; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\ReqWithHeaders\Requests\ReqWithHeaders; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class ReqWithHeadersClient diff --git a/seed/php-sdk/exhaustive/src/ReqWithHeaders/Requests/ReqWithHeaders.php b/seed/php-sdk/exhaustive/src/ReqWithHeaders/Requests/ReqWithHeaders.php index b3017a833f2..b20add89e7f 100644 --- a/seed/php-sdk/exhaustive/src/ReqWithHeaders/Requests/ReqWithHeaders.php +++ b/seed/php-sdk/exhaustive/src/ReqWithHeaders/Requests/ReqWithHeaders.php @@ -2,7 +2,7 @@ namespace Seed\ReqWithHeaders\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ReqWithHeaders extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/SeedClient.php b/seed/php-sdk/exhaustive/src/SeedClient.php index b4dc5178f68..4ce0c282e09 100644 --- a/seed/php-sdk/exhaustive/src/SeedClient.php +++ b/seed/php-sdk/exhaustive/src/SeedClient.php @@ -8,7 +8,7 @@ use Seed\NoReqBody\NoReqBodyClient; use Seed\ReqWithHeaders\ReqWithHeadersClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/exhaustive/src/Types/Object/Types/DoubleOptional.php b/seed/php-sdk/exhaustive/src/Types/Object/Types/DoubleOptional.php index e8f76212c7a..4221d312bb5 100644 --- a/seed/php-sdk/exhaustive/src/Types/Object/Types/DoubleOptional.php +++ b/seed/php-sdk/exhaustive/src/Types/Object/Types/DoubleOptional.php @@ -2,8 +2,8 @@ namespace Seed\Types\Object\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DoubleOptional extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Types/Object/Types/NestedObjectWithOptionalField.php b/seed/php-sdk/exhaustive/src/Types/Object/Types/NestedObjectWithOptionalField.php index 5a533541442..bd0fe01252d 100644 --- a/seed/php-sdk/exhaustive/src/Types/Object/Types/NestedObjectWithOptionalField.php +++ b/seed/php-sdk/exhaustive/src/Types/Object/Types/NestedObjectWithOptionalField.php @@ -2,8 +2,8 @@ namespace Seed\Types\Object\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedObjectWithOptionalField extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Types/Object/Types/NestedObjectWithRequiredField.php b/seed/php-sdk/exhaustive/src/Types/Object/Types/NestedObjectWithRequiredField.php index 13261603b4c..c62326c1082 100644 --- a/seed/php-sdk/exhaustive/src/Types/Object/Types/NestedObjectWithRequiredField.php +++ b/seed/php-sdk/exhaustive/src/Types/Object/Types/NestedObjectWithRequiredField.php @@ -2,8 +2,8 @@ namespace Seed\Types\Object\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedObjectWithRequiredField extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithMapOfMap.php b/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithMapOfMap.php index 16cf6f5b0dd..d5667f04c9d 100644 --- a/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithMapOfMap.php +++ b/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithMapOfMap.php @@ -2,9 +2,9 @@ namespace Seed\Types\Object\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ObjectWithMapOfMap extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithOptionalField.php b/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithOptionalField.php index 490a2c1cc76..d88921338f6 100644 --- a/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithOptionalField.php +++ b/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithOptionalField.php @@ -2,11 +2,11 @@ namespace Seed\Types\Object\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use DateTime; -use Seed\Core\DateType; -use Seed\Core\ArrayType; +use Seed\Core\Types\DateType; +use Seed\Core\Types\ArrayType; class ObjectWithOptionalField extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithRequiredField.php b/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithRequiredField.php index 312cf9b63b4..86fb2c2a999 100644 --- a/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithRequiredField.php +++ b/seed/php-sdk/exhaustive/src/Types/Object/Types/ObjectWithRequiredField.php @@ -2,8 +2,8 @@ namespace Seed\Types\Object\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ObjectWithRequiredField extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Types/Union/Types/Cat.php b/seed/php-sdk/exhaustive/src/Types/Union/Types/Cat.php index 6d8dfa0e8f2..64ead551c57 100644 --- a/seed/php-sdk/exhaustive/src/Types/Union/Types/Cat.php +++ b/seed/php-sdk/exhaustive/src/Types/Union/Types/Cat.php @@ -2,8 +2,8 @@ namespace Seed\Types\Union\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Cat extends SerializableType { diff --git a/seed/php-sdk/exhaustive/src/Types/Union/Types/Dog.php b/seed/php-sdk/exhaustive/src/Types/Union/Types/Dog.php index e416537908a..a3d81f1aef4 100644 --- a/seed/php-sdk/exhaustive/src/Types/Union/Types/Dog.php +++ b/seed/php-sdk/exhaustive/src/Types/Union/Types/Dog.php @@ -2,8 +2,8 @@ namespace Seed\Types\Union\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Dog extends SerializableType { diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/EnumTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/extends/src/Core/ArrayType.php b/seed/php-sdk/extends/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/extends/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/extends/src/Core/BaseApiRequest.php b/seed/php-sdk/extends/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/extends/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/extends/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/extends/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/extends/src/Core/Client/HttpMethod.php b/seed/php-sdk/extends/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/extends/src/Core/Constant.php b/seed/php-sdk/extends/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/extends/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/extends/src/Core/Json/JsonDecoder.php b/seed/php-sdk/extends/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/extends/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/extends/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/extends/src/Core/Json/JsonEncoder.php b/seed/php-sdk/extends/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/extends/src/Core/Json/SerializableType.php b/seed/php-sdk/extends/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/extends/src/Core/Json/Utils.php b/seed/php-sdk/extends/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/extends/src/Core/JsonApiRequest.php b/seed/php-sdk/extends/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/extends/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/extends/src/Core/JsonDecoder.php b/seed/php-sdk/extends/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/extends/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/extends/src/Core/JsonDeserializer.php b/seed/php-sdk/extends/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/extends/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/extends/src/Core/JsonEncoder.php b/seed/php-sdk/extends/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/extends/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/extends/src/Core/RawClient.php b/seed/php-sdk/extends/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/extends/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/extends/src/Core/SerializableType.php b/seed/php-sdk/extends/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/extends/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/extends/src/Core/Types/ArrayType.php b/seed/php-sdk/extends/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/extends/src/Core/Types/Constant.php b/seed/php-sdk/extends/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/extends/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/extends/src/Core/Union.php b/seed/php-sdk/extends/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/extends/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/extends/src/Core/Utils.php b/seed/php-sdk/extends/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/extends/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/extends/src/Requests/Inlined.php b/seed/php-sdk/extends/src/Requests/Inlined.php index d8f95c25652..3604e4273f5 100644 --- a/seed/php-sdk/extends/src/Requests/Inlined.php +++ b/seed/php-sdk/extends/src/Requests/Inlined.php @@ -2,8 +2,8 @@ namespace Seed\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Inlined extends SerializableType { diff --git a/seed/php-sdk/extends/src/SeedClient.php b/seed/php-sdk/extends/src/SeedClient.php index 8c0c471adfe..838cb2f2845 100644 --- a/seed/php-sdk/extends/src/SeedClient.php +++ b/seed/php-sdk/extends/src/SeedClient.php @@ -3,12 +3,12 @@ namespace Seed; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Requests\Inlined; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class SeedClient diff --git a/seed/php-sdk/extends/src/Types/Docs.php b/seed/php-sdk/extends/src/Types/Docs.php index ac3fcaf19b3..6b18865a76e 100644 --- a/seed/php-sdk/extends/src/Types/Docs.php +++ b/seed/php-sdk/extends/src/Types/Docs.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Docs extends SerializableType { diff --git a/seed/php-sdk/extends/src/Types/ExampleType.php b/seed/php-sdk/extends/src/Types/ExampleType.php index 269f61c8183..a4fc3727361 100644 --- a/seed/php-sdk/extends/src/Types/ExampleType.php +++ b/seed/php-sdk/extends/src/Types/ExampleType.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExampleType extends SerializableType { diff --git a/seed/php-sdk/extends/src/Types/Json.php b/seed/php-sdk/extends/src/Types/Json.php index f0f6099c809..0d77f46baa0 100644 --- a/seed/php-sdk/extends/src/Types/Json.php +++ b/seed/php-sdk/extends/src/Types/Json.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Json extends SerializableType { diff --git a/seed/php-sdk/extends/src/Types/NestedType.php b/seed/php-sdk/extends/src/Types/NestedType.php index 492b7e2e420..54528cc85b4 100644 --- a/seed/php-sdk/extends/src/Types/NestedType.php +++ b/seed/php-sdk/extends/src/Types/NestedType.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedType extends SerializableType { diff --git a/seed/php-sdk/extends/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/extends/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/extends/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/EnumTest.php b/seed/php-sdk/extends/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/extends/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/extends/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/extends/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/extends/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/extends/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/extends/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/extends/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/extends/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/ArrayType.php b/seed/php-sdk/extra-properties/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/BaseApiRequest.php b/seed/php-sdk/extra-properties/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/extra-properties/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Client/HttpMethod.php b/seed/php-sdk/extra-properties/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Constant.php b/seed/php-sdk/extra-properties/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Json/JsonDecoder.php b/seed/php-sdk/extra-properties/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/extra-properties/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Json/JsonEncoder.php b/seed/php-sdk/extra-properties/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Json/SerializableType.php b/seed/php-sdk/extra-properties/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Json/Utils.php b/seed/php-sdk/extra-properties/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/JsonApiRequest.php b/seed/php-sdk/extra-properties/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/JsonDecoder.php b/seed/php-sdk/extra-properties/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/JsonDeserializer.php b/seed/php-sdk/extra-properties/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/JsonEncoder.php b/seed/php-sdk/extra-properties/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/RawClient.php b/seed/php-sdk/extra-properties/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/SerializableType.php b/seed/php-sdk/extra-properties/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/Types/ArrayType.php b/seed/php-sdk/extra-properties/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Types/Constant.php b/seed/php-sdk/extra-properties/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/extra-properties/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/extra-properties/src/Core/Union.php b/seed/php-sdk/extra-properties/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/extra-properties/src/Core/Utils.php b/seed/php-sdk/extra-properties/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/extra-properties/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/extra-properties/src/SeedClient.php b/seed/php-sdk/extra-properties/src/SeedClient.php index be7d76e3425..b69d9150d63 100644 --- a/seed/php-sdk/extra-properties/src/SeedClient.php +++ b/seed/php-sdk/extra-properties/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\User\UserClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/extra-properties/src/Types/Failure.php b/seed/php-sdk/extra-properties/src/Types/Failure.php index cc12889989d..4e6f5ef9232 100644 --- a/seed/php-sdk/extra-properties/src/Types/Failure.php +++ b/seed/php-sdk/extra-properties/src/Types/Failure.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Failure extends SerializableType { diff --git a/seed/php-sdk/extra-properties/src/User/Requests/CreateUserRequest.php b/seed/php-sdk/extra-properties/src/User/Requests/CreateUserRequest.php index 8342041c3aa..34147c44061 100644 --- a/seed/php-sdk/extra-properties/src/User/Requests/CreateUserRequest.php +++ b/seed/php-sdk/extra-properties/src/User/Requests/CreateUserRequest.php @@ -2,8 +2,8 @@ namespace Seed\User\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CreateUserRequest extends SerializableType { diff --git a/seed/php-sdk/extra-properties/src/User/Types/User.php b/seed/php-sdk/extra-properties/src/User/Types/User.php index e7f7d2b0e29..a82acc6c81e 100644 --- a/seed/php-sdk/extra-properties/src/User/Types/User.php +++ b/seed/php-sdk/extra-properties/src/User/Types/User.php @@ -2,8 +2,8 @@ namespace Seed\User\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-sdk/extra-properties/src/User/UserClient.php b/seed/php-sdk/extra-properties/src/User/UserClient.php index 1d1ffde1156..eb5d471e61d 100644 --- a/seed/php-sdk/extra-properties/src/User/UserClient.php +++ b/seed/php-sdk/extra-properties/src/User/UserClient.php @@ -2,13 +2,13 @@ namespace Seed\User; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\User\Requests\CreateUserRequest; use Seed\User\Types\User; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/EnumTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/extra-properties/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/extra-properties/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/file-download/src/Core/ArrayType.php b/seed/php-sdk/file-download/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/file-download/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/file-download/src/Core/BaseApiRequest.php b/seed/php-sdk/file-download/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/file-download/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/file-download/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/file-download/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/file-download/src/Core/Client/HttpMethod.php b/seed/php-sdk/file-download/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/file-download/src/Core/Constant.php b/seed/php-sdk/file-download/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/file-download/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/file-download/src/Core/Json/JsonDecoder.php b/seed/php-sdk/file-download/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/file-download/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/file-download/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/file-download/src/Core/Json/JsonEncoder.php b/seed/php-sdk/file-download/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/file-download/src/Core/Json/SerializableType.php b/seed/php-sdk/file-download/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/file-download/src/Core/Json/Utils.php b/seed/php-sdk/file-download/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/file-download/src/Core/JsonApiRequest.php b/seed/php-sdk/file-download/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/file-download/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/file-download/src/Core/JsonDecoder.php b/seed/php-sdk/file-download/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/file-download/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/file-download/src/Core/JsonDeserializer.php b/seed/php-sdk/file-download/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/file-download/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/file-download/src/Core/JsonEncoder.php b/seed/php-sdk/file-download/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/file-download/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/file-download/src/Core/RawClient.php b/seed/php-sdk/file-download/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/file-download/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/file-download/src/Core/SerializableType.php b/seed/php-sdk/file-download/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/file-download/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/file-download/src/Core/Types/ArrayType.php b/seed/php-sdk/file-download/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/file-download/src/Core/Types/Constant.php b/seed/php-sdk/file-download/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/file-download/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/file-download/src/Core/Union.php b/seed/php-sdk/file-download/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/file-download/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/file-download/src/Core/Utils.php b/seed/php-sdk/file-download/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/file-download/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/file-download/src/SeedClient.php b/seed/php-sdk/file-download/src/SeedClient.php index 5f052555c44..4ca47b660f7 100644 --- a/seed/php-sdk/file-download/src/SeedClient.php +++ b/seed/php-sdk/file-download/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/file-download/src/Service/ServiceClient.php b/seed/php-sdk/file-download/src/Service/ServiceClient.php index 1995642132a..3617e18eb30 100644 --- a/seed/php-sdk/file-download/src/Service/ServiceClient.php +++ b/seed/php-sdk/file-download/src/Service/ServiceClient.php @@ -2,11 +2,11 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class ServiceClient diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/file-download/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/EnumTest.php b/seed/php-sdk/file-download/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/file-download/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/file-download/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/file-download/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/file-download/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/file-download/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/file-download/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/file-download/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/file-upload/src/Core/ArrayType.php b/seed/php-sdk/file-upload/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/file-upload/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/file-upload/src/Core/BaseApiRequest.php b/seed/php-sdk/file-upload/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/file-upload/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/file-upload/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/file-upload/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Client/HttpMethod.php b/seed/php-sdk/file-upload/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Constant.php b/seed/php-sdk/file-upload/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/file-upload/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Json/JsonDecoder.php b/seed/php-sdk/file-upload/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/file-upload/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Json/JsonEncoder.php b/seed/php-sdk/file-upload/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Json/SerializableType.php b/seed/php-sdk/file-upload/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Json/Utils.php b/seed/php-sdk/file-upload/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/file-upload/src/Core/JsonApiRequest.php b/seed/php-sdk/file-upload/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/file-upload/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/file-upload/src/Core/JsonDecoder.php b/seed/php-sdk/file-upload/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/file-upload/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/file-upload/src/Core/JsonDeserializer.php b/seed/php-sdk/file-upload/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/file-upload/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/file-upload/src/Core/JsonEncoder.php b/seed/php-sdk/file-upload/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/file-upload/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/file-upload/src/Core/RawClient.php b/seed/php-sdk/file-upload/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/file-upload/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/file-upload/src/Core/SerializableType.php b/seed/php-sdk/file-upload/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/file-upload/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/file-upload/src/Core/Types/ArrayType.php b/seed/php-sdk/file-upload/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Types/Constant.php b/seed/php-sdk/file-upload/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/file-upload/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/file-upload/src/Core/Union.php b/seed/php-sdk/file-upload/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/file-upload/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/file-upload/src/Core/Utils.php b/seed/php-sdk/file-upload/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/file-upload/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/file-upload/src/SeedClient.php b/seed/php-sdk/file-upload/src/SeedClient.php index 5f052555c44..4ca47b660f7 100644 --- a/seed/php-sdk/file-upload/src/SeedClient.php +++ b/seed/php-sdk/file-upload/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/file-upload/src/Service/Requests/JustFileRequet.php b/seed/php-sdk/file-upload/src/Service/Requests/JustFileRequet.php index 6e8263238a4..9a45931dd57 100644 --- a/seed/php-sdk/file-upload/src/Service/Requests/JustFileRequet.php +++ b/seed/php-sdk/file-upload/src/Service/Requests/JustFileRequet.php @@ -2,7 +2,7 @@ namespace Seed\Service\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class JustFileRequet extends SerializableType { diff --git a/seed/php-sdk/file-upload/src/Service/Requests/JustFileWithQueryParamsRequet.php b/seed/php-sdk/file-upload/src/Service/Requests/JustFileWithQueryParamsRequet.php index 74ec3254cbb..bcfd24b385e 100644 --- a/seed/php-sdk/file-upload/src/Service/Requests/JustFileWithQueryParamsRequet.php +++ b/seed/php-sdk/file-upload/src/Service/Requests/JustFileWithQueryParamsRequet.php @@ -2,7 +2,7 @@ namespace Seed\Service\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class JustFileWithQueryParamsRequet extends SerializableType { diff --git a/seed/php-sdk/file-upload/src/Service/Requests/MyRequest.php b/seed/php-sdk/file-upload/src/Service/Requests/MyRequest.php index dbf8e18b8ea..e31a5577856 100644 --- a/seed/php-sdk/file-upload/src/Service/Requests/MyRequest.php +++ b/seed/php-sdk/file-upload/src/Service/Requests/MyRequest.php @@ -2,7 +2,7 @@ namespace Seed\Service\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class MyRequest extends SerializableType { diff --git a/seed/php-sdk/file-upload/src/Service/Requests/WithContentTypeRequest.php b/seed/php-sdk/file-upload/src/Service/Requests/WithContentTypeRequest.php index 982244dbff2..a7c5bbb1b6e 100644 --- a/seed/php-sdk/file-upload/src/Service/Requests/WithContentTypeRequest.php +++ b/seed/php-sdk/file-upload/src/Service/Requests/WithContentTypeRequest.php @@ -2,7 +2,7 @@ namespace Seed\Service\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class WithContentTypeRequest extends SerializableType { diff --git a/seed/php-sdk/file-upload/src/Service/ServiceClient.php b/seed/php-sdk/file-upload/src/Service/ServiceClient.php index 007b916d15a..21c944bf8e5 100644 --- a/seed/php-sdk/file-upload/src/Service/ServiceClient.php +++ b/seed/php-sdk/file-upload/src/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Service\Requests\MyRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; use Seed\Service\Requests\JustFileRequet; use Seed\Service\Requests\JustFileWithQueryParamsRequet; diff --git a/seed/php-sdk/file-upload/src/Service/Types/MyObject.php b/seed/php-sdk/file-upload/src/Service/Types/MyObject.php index cef04acb1ed..b8ad1e54aa0 100644 --- a/seed/php-sdk/file-upload/src/Service/Types/MyObject.php +++ b/seed/php-sdk/file-upload/src/Service/Types/MyObject.php @@ -2,8 +2,8 @@ namespace Seed\Service\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class MyObject extends SerializableType { diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/EnumTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/file-upload/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/file-upload/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/folders/src/A/AClient.php b/seed/php-sdk/folders/src/A/AClient.php index 0efe60cd0e4..266a7b843c9 100644 --- a/seed/php-sdk/folders/src/A/AClient.php +++ b/seed/php-sdk/folders/src/A/AClient.php @@ -4,7 +4,7 @@ use Seed\A\B\BClient; use Seed\A\C\CClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class AClient { diff --git a/seed/php-sdk/folders/src/A/B/BClient.php b/seed/php-sdk/folders/src/A/B/BClient.php index 677a8d13b64..6483b0531c0 100644 --- a/seed/php-sdk/folders/src/A/B/BClient.php +++ b/seed/php-sdk/folders/src/A/B/BClient.php @@ -2,11 +2,11 @@ namespace Seed\A\B; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class BClient diff --git a/seed/php-sdk/folders/src/A/C/CClient.php b/seed/php-sdk/folders/src/A/C/CClient.php index 92d782324c4..a359d4fe753 100644 --- a/seed/php-sdk/folders/src/A/C/CClient.php +++ b/seed/php-sdk/folders/src/A/C/CClient.php @@ -2,11 +2,11 @@ namespace Seed\A\C; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class CClient diff --git a/seed/php-sdk/folders/src/Core/ArrayType.php b/seed/php-sdk/folders/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/folders/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/folders/src/Core/BaseApiRequest.php b/seed/php-sdk/folders/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/folders/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/folders/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/folders/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/folders/src/Core/Client/HttpMethod.php b/seed/php-sdk/folders/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/folders/src/Core/Constant.php b/seed/php-sdk/folders/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/folders/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/folders/src/Core/Json/JsonDecoder.php b/seed/php-sdk/folders/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/folders/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/folders/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/folders/src/Core/Json/JsonEncoder.php b/seed/php-sdk/folders/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/folders/src/Core/Json/SerializableType.php b/seed/php-sdk/folders/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/folders/src/Core/Json/Utils.php b/seed/php-sdk/folders/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/folders/src/Core/JsonApiRequest.php b/seed/php-sdk/folders/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/folders/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/folders/src/Core/JsonDecoder.php b/seed/php-sdk/folders/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/folders/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/folders/src/Core/JsonDeserializer.php b/seed/php-sdk/folders/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/folders/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/folders/src/Core/JsonEncoder.php b/seed/php-sdk/folders/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/folders/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/folders/src/Core/RawClient.php b/seed/php-sdk/folders/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/folders/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/folders/src/Core/SerializableType.php b/seed/php-sdk/folders/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/folders/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/folders/src/Core/Types/ArrayType.php b/seed/php-sdk/folders/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/folders/src/Core/Types/Constant.php b/seed/php-sdk/folders/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/folders/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/folders/src/Core/Union.php b/seed/php-sdk/folders/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/folders/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/folders/src/Core/Utils.php b/seed/php-sdk/folders/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/folders/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/folders/src/Folder/FolderClient.php b/seed/php-sdk/folders/src/Folder/FolderClient.php index 97d678efcca..21668400e9b 100644 --- a/seed/php-sdk/folders/src/Folder/FolderClient.php +++ b/seed/php-sdk/folders/src/Folder/FolderClient.php @@ -3,11 +3,11 @@ namespace Seed\Folder; use Seed\Folder\Service\ServiceClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class FolderClient diff --git a/seed/php-sdk/folders/src/Folder/Service/ServiceClient.php b/seed/php-sdk/folders/src/Folder/Service/ServiceClient.php index eb28b2c0f47..7731527043f 100644 --- a/seed/php-sdk/folders/src/Folder/Service/ServiceClient.php +++ b/seed/php-sdk/folders/src/Folder/Service/ServiceClient.php @@ -2,11 +2,11 @@ namespace Seed\Folder\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class ServiceClient diff --git a/seed/php-sdk/folders/src/SeedClient.php b/seed/php-sdk/folders/src/SeedClient.php index 1d1671c81fd..faaa145f25b 100644 --- a/seed/php-sdk/folders/src/SeedClient.php +++ b/seed/php-sdk/folders/src/SeedClient.php @@ -5,11 +5,11 @@ use Seed\A\AClient; use Seed\Folder\FolderClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class SeedClient diff --git a/seed/php-sdk/folders/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/folders/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/folders/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/EnumTest.php b/seed/php-sdk/folders/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/folders/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/folders/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/folders/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/folders/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/folders/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/folders/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/folders/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/folders/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/ArrayType.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/BaseApiRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/HttpMethod.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Constant.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonDecoder.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonEncoder.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/SerializableType.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/Utils.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonApiRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDecoder.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDeserializer.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonEncoder.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/RawClient.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/SerializableType.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/ArrayType.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/Constant.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Union.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Utils.php b/seed/php-sdk/grpc-proto-exhaustive/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/DataserviceClient.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/DataserviceClient.php index 91b0b648d4c..b7c93f9ef7a 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/DataserviceClient.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/DataserviceClient.php @@ -2,13 +2,13 @@ namespace Seed\Dataservice; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Dataservice\Requests\UploadRequest; use Seed\Types\UploadResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Dataservice\Requests\DeleteRequest; diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php index 2882f1b0477..0d359076619 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php @@ -2,10 +2,10 @@ namespace Seed\Dataservice\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class DeleteRequest extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php index 0706e7ce2ff..0cd9085a962 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php @@ -2,9 +2,9 @@ namespace Seed\Dataservice\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\Union; class DescribeRequest extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/FetchRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/FetchRequest.php index 005148492f7..dd356e162c6 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/FetchRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/FetchRequest.php @@ -2,7 +2,7 @@ namespace Seed\Dataservice\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class FetchRequest extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/ListRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/ListRequest.php index d15d7afb255..898a9b242df 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/ListRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/ListRequest.php @@ -2,7 +2,7 @@ namespace Seed\Dataservice\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ListRequest extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php index 88f2351d064..97d070f8df1 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php @@ -2,11 +2,11 @@ namespace Seed\Dataservice\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\Union; use Seed\Types\QueryColumn; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Types\IndexedData; class QueryRequest extends SerializableType diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php index 21a4c59b468..f22e7e9f0dd 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php @@ -2,10 +2,10 @@ namespace Seed\Dataservice\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; use Seed\Types\IndexedData; class UpdateRequest extends SerializableType diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UploadRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UploadRequest.php index ab91c447fae..8418104da03 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UploadRequest.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UploadRequest.php @@ -2,10 +2,10 @@ namespace Seed\Dataservice\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Types\Column; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UploadRequest extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/SeedClient.php b/seed/php-sdk/grpc-proto-exhaustive/src/SeedClient.php index 459e39ad761..42e21af2333 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/SeedClient.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Dataservice\DataserviceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php index c696c3d3bc3..55cff2db1bd 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php @@ -2,10 +2,10 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class Column extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/DeleteResponse.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/DeleteResponse.php index 0802b3d9ccf..7929f23ab6a 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/DeleteResponse.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/DeleteResponse.php @@ -2,7 +2,7 @@ namespace Seed\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class DeleteResponse extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/DescribeResponse.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/DescribeResponse.php index f01690b2162..c6649dd18ff 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/DescribeResponse.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/DescribeResponse.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DescribeResponse extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/FetchResponse.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/FetchResponse.php index eeeac763e78..4ffae0f3c74 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/FetchResponse.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/FetchResponse.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class FetchResponse extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/IndexedData.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/IndexedData.php index dcd58559a6f..f82b94b2982 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/IndexedData.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/IndexedData.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class IndexedData extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ListElement.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ListElement.php index f543141f836..a3db493d256 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ListElement.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ListElement.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ListElement extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ListResponse.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ListResponse.php index 9fd58565caf..f18dbc3425f 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ListResponse.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ListResponse.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ListResponse extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/NamespaceSummary.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/NamespaceSummary.php index 9166a90e772..73f16ebb9f1 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/NamespaceSummary.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/NamespaceSummary.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NamespaceSummary extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Pagination.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Pagination.php index 5821b4052e0..ab327b4ef2b 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Pagination.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Pagination.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Pagination extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php index 27e8ae75d26..bbb38cc7574 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php @@ -2,10 +2,10 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class QueryColumn extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryResponse.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryResponse.php index 739b1aa8bc8..ed915b5304d 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryResponse.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryResponse.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class QueryResponse extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryResult.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryResult.php index dd3df7ddf6f..e0fcdd118ed 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryResult.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryResult.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class QueryResult extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php index 9c0399df337..24fd20f9d9d 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php @@ -2,10 +2,10 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class ScoredColumn extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/UpdateResponse.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/UpdateResponse.php index 25a8c72f72c..691fd190f25 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/UpdateResponse.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/UpdateResponse.php @@ -2,7 +2,7 @@ namespace Seed\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class UpdateResponse extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/UploadResponse.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/UploadResponse.php index 5bc23a14b3e..301e55c75e1 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/UploadResponse.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/UploadResponse.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UploadResponse extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Usage.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Usage.php index 7123ddb9326..21780235eb1 100644 --- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Usage.php +++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Usage.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Usage extends SerializableType { diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/EnumTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/ArrayType.php b/seed/php-sdk/grpc-proto/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/BaseApiRequest.php b/seed/php-sdk/grpc-proto/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/grpc-proto/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Client/HttpMethod.php b/seed/php-sdk/grpc-proto/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Constant.php b/seed/php-sdk/grpc-proto/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Json/JsonDecoder.php b/seed/php-sdk/grpc-proto/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/grpc-proto/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Json/JsonEncoder.php b/seed/php-sdk/grpc-proto/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Json/SerializableType.php b/seed/php-sdk/grpc-proto/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Json/Utils.php b/seed/php-sdk/grpc-proto/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/JsonApiRequest.php b/seed/php-sdk/grpc-proto/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/JsonDecoder.php b/seed/php-sdk/grpc-proto/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/JsonDeserializer.php b/seed/php-sdk/grpc-proto/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/JsonEncoder.php b/seed/php-sdk/grpc-proto/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/RawClient.php b/seed/php-sdk/grpc-proto/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/SerializableType.php b/seed/php-sdk/grpc-proto/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/Types/ArrayType.php b/seed/php-sdk/grpc-proto/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Types/Constant.php b/seed/php-sdk/grpc-proto/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/grpc-proto/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/grpc-proto/src/Core/Union.php b/seed/php-sdk/grpc-proto/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/grpc-proto/src/Core/Utils.php b/seed/php-sdk/grpc-proto/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/grpc-proto/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/grpc-proto/src/SeedClient.php b/seed/php-sdk/grpc-proto/src/SeedClient.php index ec332ca8e3f..aa6c587dcdd 100644 --- a/seed/php-sdk/grpc-proto/src/SeedClient.php +++ b/seed/php-sdk/grpc-proto/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Userservice\UserserviceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/grpc-proto/src/Types/CreateResponse.php b/seed/php-sdk/grpc-proto/src/Types/CreateResponse.php index e5fb2d3b4dd..7d8e9081aea 100644 --- a/seed/php-sdk/grpc-proto/src/Types/CreateResponse.php +++ b/seed/php-sdk/grpc-proto/src/Types/CreateResponse.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CreateResponse extends SerializableType { diff --git a/seed/php-sdk/grpc-proto/src/Types/UserModel.php b/seed/php-sdk/grpc-proto/src/Types/UserModel.php index cff03d8ec8c..8893c382c25 100644 --- a/seed/php-sdk/grpc-proto/src/Types/UserModel.php +++ b/seed/php-sdk/grpc-proto/src/Types/UserModel.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\Union; class UserModel extends SerializableType { diff --git a/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php b/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php index a239f9835bc..bc0cbd5c034 100644 --- a/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php +++ b/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php @@ -2,9 +2,9 @@ namespace Seed\Userservice\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\Union; class CreateRequest extends SerializableType { diff --git a/seed/php-sdk/grpc-proto/src/Userservice/UserserviceClient.php b/seed/php-sdk/grpc-proto/src/Userservice/UserserviceClient.php index a8950b78cca..fd15d18106c 100644 --- a/seed/php-sdk/grpc-proto/src/Userservice/UserserviceClient.php +++ b/seed/php-sdk/grpc-proto/src/Userservice/UserserviceClient.php @@ -2,13 +2,13 @@ namespace Seed\Userservice; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Userservice\Requests\CreateRequest; use Seed\Types\CreateResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/EnumTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/grpc-proto/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/grpc-proto/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/ArrayType.php b/seed/php-sdk/idempotency-headers/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/BaseApiRequest.php b/seed/php-sdk/idempotency-headers/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/idempotency-headers/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Client/HttpMethod.php b/seed/php-sdk/idempotency-headers/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Constant.php b/seed/php-sdk/idempotency-headers/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Json/JsonDecoder.php b/seed/php-sdk/idempotency-headers/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/idempotency-headers/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Json/JsonEncoder.php b/seed/php-sdk/idempotency-headers/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Json/SerializableType.php b/seed/php-sdk/idempotency-headers/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Json/Utils.php b/seed/php-sdk/idempotency-headers/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/JsonApiRequest.php b/seed/php-sdk/idempotency-headers/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/JsonDecoder.php b/seed/php-sdk/idempotency-headers/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/JsonDeserializer.php b/seed/php-sdk/idempotency-headers/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/JsonEncoder.php b/seed/php-sdk/idempotency-headers/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/RawClient.php b/seed/php-sdk/idempotency-headers/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/SerializableType.php b/seed/php-sdk/idempotency-headers/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Types/ArrayType.php b/seed/php-sdk/idempotency-headers/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Types/Constant.php b/seed/php-sdk/idempotency-headers/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/idempotency-headers/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Union.php b/seed/php-sdk/idempotency-headers/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Core/Utils.php b/seed/php-sdk/idempotency-headers/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/idempotency-headers/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/idempotency-headers/src/Payment/PaymentClient.php b/seed/php-sdk/idempotency-headers/src/Payment/PaymentClient.php index 529e0197d43..e38afbabc15 100644 --- a/seed/php-sdk/idempotency-headers/src/Payment/PaymentClient.php +++ b/seed/php-sdk/idempotency-headers/src/Payment/PaymentClient.php @@ -2,13 +2,13 @@ namespace Seed\Payment; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Payment\Requests\CreatePaymentRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/idempotency-headers/src/Payment/Requests/CreatePaymentRequest.php b/seed/php-sdk/idempotency-headers/src/Payment/Requests/CreatePaymentRequest.php index 7f10647afdd..65712d88080 100644 --- a/seed/php-sdk/idempotency-headers/src/Payment/Requests/CreatePaymentRequest.php +++ b/seed/php-sdk/idempotency-headers/src/Payment/Requests/CreatePaymentRequest.php @@ -2,8 +2,8 @@ namespace Seed\Payment\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Payment\Types\Currency; class CreatePaymentRequest extends SerializableType diff --git a/seed/php-sdk/idempotency-headers/src/SeedClient.php b/seed/php-sdk/idempotency-headers/src/SeedClient.php index 10cbe29c8b3..dca74338cc3 100644 --- a/seed/php-sdk/idempotency-headers/src/SeedClient.php +++ b/seed/php-sdk/idempotency-headers/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Payment\PaymentClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/EnumTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/idempotency-headers/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/imdb/src/Core/ArrayType.php b/seed/php-sdk/imdb/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/imdb/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/imdb/src/Core/BaseApiRequest.php b/seed/php-sdk/imdb/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/imdb/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/imdb/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/imdb/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/imdb/src/Core/Client/HttpMethod.php b/seed/php-sdk/imdb/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/imdb/src/Core/Constant.php b/seed/php-sdk/imdb/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/imdb/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/imdb/src/Core/Json/JsonDecoder.php b/seed/php-sdk/imdb/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/imdb/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/imdb/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/imdb/src/Core/Json/JsonEncoder.php b/seed/php-sdk/imdb/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/imdb/src/Core/Json/SerializableType.php b/seed/php-sdk/imdb/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/imdb/src/Core/Json/Utils.php b/seed/php-sdk/imdb/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/imdb/src/Core/JsonApiRequest.php b/seed/php-sdk/imdb/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/imdb/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/imdb/src/Core/JsonDecoder.php b/seed/php-sdk/imdb/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/imdb/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/imdb/src/Core/JsonDeserializer.php b/seed/php-sdk/imdb/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/imdb/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/imdb/src/Core/JsonEncoder.php b/seed/php-sdk/imdb/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/imdb/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/imdb/src/Core/RawClient.php b/seed/php-sdk/imdb/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/imdb/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/imdb/src/Core/SerializableType.php b/seed/php-sdk/imdb/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/imdb/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/imdb/src/Core/Types/ArrayType.php b/seed/php-sdk/imdb/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/imdb/src/Core/Types/Constant.php b/seed/php-sdk/imdb/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/imdb/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/imdb/src/Core/Union.php b/seed/php-sdk/imdb/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/imdb/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/imdb/src/Core/Utils.php b/seed/php-sdk/imdb/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/imdb/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/imdb/src/Imdb/ImdbClient.php b/seed/php-sdk/imdb/src/Imdb/ImdbClient.php index d6a36a753f3..07fe8673461 100644 --- a/seed/php-sdk/imdb/src/Imdb/ImdbClient.php +++ b/seed/php-sdk/imdb/src/Imdb/ImdbClient.php @@ -2,13 +2,13 @@ namespace Seed\Imdb; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Imdb\Types\CreateMovieRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Imdb\Types\Movie; diff --git a/seed/php-sdk/imdb/src/Imdb/Types/CreateMovieRequest.php b/seed/php-sdk/imdb/src/Imdb/Types/CreateMovieRequest.php index cc6c93cca3a..f8c1173fe41 100644 --- a/seed/php-sdk/imdb/src/Imdb/Types/CreateMovieRequest.php +++ b/seed/php-sdk/imdb/src/Imdb/Types/CreateMovieRequest.php @@ -2,8 +2,8 @@ namespace Seed\Imdb\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CreateMovieRequest extends SerializableType { diff --git a/seed/php-sdk/imdb/src/Imdb/Types/Movie.php b/seed/php-sdk/imdb/src/Imdb/Types/Movie.php index a8d9f63462f..6e8f66173ff 100644 --- a/seed/php-sdk/imdb/src/Imdb/Types/Movie.php +++ b/seed/php-sdk/imdb/src/Imdb/Types/Movie.php @@ -2,8 +2,8 @@ namespace Seed\Imdb\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Movie extends SerializableType { diff --git a/seed/php-sdk/imdb/src/SeedClient.php b/seed/php-sdk/imdb/src/SeedClient.php index ea1ae43f96e..797a2e4b470 100644 --- a/seed/php-sdk/imdb/src/SeedClient.php +++ b/seed/php-sdk/imdb/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Imdb\ImdbClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/imdb/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/EnumTest.php b/seed/php-sdk/imdb/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/imdb/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/imdb/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/imdb/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/imdb/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/imdb/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/imdb/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/imdb/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/literal/src/Core/ArrayType.php b/seed/php-sdk/literal/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/literal/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/literal/src/Core/BaseApiRequest.php b/seed/php-sdk/literal/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/literal/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/literal/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/literal/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/literal/src/Core/Client/HttpMethod.php b/seed/php-sdk/literal/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/literal/src/Core/Constant.php b/seed/php-sdk/literal/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/literal/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/literal/src/Core/Json/JsonDecoder.php b/seed/php-sdk/literal/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/literal/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/literal/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/literal/src/Core/Json/JsonEncoder.php b/seed/php-sdk/literal/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/literal/src/Core/Json/SerializableType.php b/seed/php-sdk/literal/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/literal/src/Core/Json/Utils.php b/seed/php-sdk/literal/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/literal/src/Core/JsonApiRequest.php b/seed/php-sdk/literal/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/literal/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/literal/src/Core/JsonDecoder.php b/seed/php-sdk/literal/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/literal/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/literal/src/Core/JsonDeserializer.php b/seed/php-sdk/literal/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/literal/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/literal/src/Core/JsonEncoder.php b/seed/php-sdk/literal/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/literal/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/literal/src/Core/RawClient.php b/seed/php-sdk/literal/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/literal/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/literal/src/Core/SerializableType.php b/seed/php-sdk/literal/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/literal/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/literal/src/Core/Types/ArrayType.php b/seed/php-sdk/literal/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/literal/src/Core/Types/Constant.php b/seed/php-sdk/literal/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/literal/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/literal/src/Core/Union.php b/seed/php-sdk/literal/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/literal/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/literal/src/Core/Utils.php b/seed/php-sdk/literal/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/literal/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/literal/src/Headers/HeadersClient.php b/seed/php-sdk/literal/src/Headers/HeadersClient.php index 7229d35e106..6bd28dd8e47 100644 --- a/seed/php-sdk/literal/src/Headers/HeadersClient.php +++ b/seed/php-sdk/literal/src/Headers/HeadersClient.php @@ -2,13 +2,13 @@ namespace Seed\Headers; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Headers\Requests\SendLiteralsInHeadersRequest; use Seed\Types\SendResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/literal/src/Headers/Requests/SendLiteralsInHeadersRequest.php b/seed/php-sdk/literal/src/Headers/Requests/SendLiteralsInHeadersRequest.php index 7fbaf457b83..ab795f9a1f4 100644 --- a/seed/php-sdk/literal/src/Headers/Requests/SendLiteralsInHeadersRequest.php +++ b/seed/php-sdk/literal/src/Headers/Requests/SendLiteralsInHeadersRequest.php @@ -2,8 +2,8 @@ namespace Seed\Headers\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SendLiteralsInHeadersRequest extends SerializableType { diff --git a/seed/php-sdk/literal/src/Inlined/InlinedClient.php b/seed/php-sdk/literal/src/Inlined/InlinedClient.php index 0b221b3cb14..33885465796 100644 --- a/seed/php-sdk/literal/src/Inlined/InlinedClient.php +++ b/seed/php-sdk/literal/src/Inlined/InlinedClient.php @@ -2,13 +2,13 @@ namespace Seed\Inlined; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Inlined\Requests\SendLiteralsInlinedRequest; use Seed\Types\SendResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/literal/src/Inlined/Requests/SendLiteralsInlinedRequest.php b/seed/php-sdk/literal/src/Inlined/Requests/SendLiteralsInlinedRequest.php index 925656b9305..cd3bb9eab5b 100644 --- a/seed/php-sdk/literal/src/Inlined/Requests/SendLiteralsInlinedRequest.php +++ b/seed/php-sdk/literal/src/Inlined/Requests/SendLiteralsInlinedRequest.php @@ -2,8 +2,8 @@ namespace Seed\Inlined\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Inlined\Types\ATopLevelLiteral; class SendLiteralsInlinedRequest extends SerializableType diff --git a/seed/php-sdk/literal/src/Inlined/Types/ANestedLiteral.php b/seed/php-sdk/literal/src/Inlined/Types/ANestedLiteral.php index 062e599fa24..28d36df2deb 100644 --- a/seed/php-sdk/literal/src/Inlined/Types/ANestedLiteral.php +++ b/seed/php-sdk/literal/src/Inlined/Types/ANestedLiteral.php @@ -2,8 +2,8 @@ namespace Seed\Inlined\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ANestedLiteral extends SerializableType { diff --git a/seed/php-sdk/literal/src/Inlined/Types/ATopLevelLiteral.php b/seed/php-sdk/literal/src/Inlined/Types/ATopLevelLiteral.php index 517708fcdc3..3040d747101 100644 --- a/seed/php-sdk/literal/src/Inlined/Types/ATopLevelLiteral.php +++ b/seed/php-sdk/literal/src/Inlined/Types/ATopLevelLiteral.php @@ -2,8 +2,8 @@ namespace Seed\Inlined\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ATopLevelLiteral extends SerializableType { diff --git a/seed/php-sdk/literal/src/Path/PathClient.php b/seed/php-sdk/literal/src/Path/PathClient.php index 9ddda3562c6..f6a9f6a4157 100644 --- a/seed/php-sdk/literal/src/Path/PathClient.php +++ b/seed/php-sdk/literal/src/Path/PathClient.php @@ -2,12 +2,12 @@ namespace Seed\Path; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Types\SendResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/literal/src/Query/QueryClient.php b/seed/php-sdk/literal/src/Query/QueryClient.php index 4eee9567e34..56cfd945c85 100644 --- a/seed/php-sdk/literal/src/Query/QueryClient.php +++ b/seed/php-sdk/literal/src/Query/QueryClient.php @@ -2,13 +2,13 @@ namespace Seed\Query; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Query\Requests\SendLiteralsInQueryRequest; use Seed\Types\SendResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/literal/src/Query/Requests/SendLiteralsInQueryRequest.php b/seed/php-sdk/literal/src/Query/Requests/SendLiteralsInQueryRequest.php index a63aea41d98..1efeb55735b 100644 --- a/seed/php-sdk/literal/src/Query/Requests/SendLiteralsInQueryRequest.php +++ b/seed/php-sdk/literal/src/Query/Requests/SendLiteralsInQueryRequest.php @@ -2,7 +2,7 @@ namespace Seed\Query\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class SendLiteralsInQueryRequest extends SerializableType { diff --git a/seed/php-sdk/literal/src/Reference/ReferenceClient.php b/seed/php-sdk/literal/src/Reference/ReferenceClient.php index 31838b463e6..5d468429589 100644 --- a/seed/php-sdk/literal/src/Reference/ReferenceClient.php +++ b/seed/php-sdk/literal/src/Reference/ReferenceClient.php @@ -2,13 +2,13 @@ namespace Seed\Reference; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Reference\Types\SendRequest; use Seed\Types\SendResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/literal/src/Reference/Types/ContainerObject.php b/seed/php-sdk/literal/src/Reference/Types/ContainerObject.php index 4d9ac4d991f..b60612a7531 100644 --- a/seed/php-sdk/literal/src/Reference/Types/ContainerObject.php +++ b/seed/php-sdk/literal/src/Reference/Types/ContainerObject.php @@ -2,9 +2,9 @@ namespace Seed\Reference\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ContainerObject extends SerializableType { diff --git a/seed/php-sdk/literal/src/Reference/Types/NestedObjectWithLiterals.php b/seed/php-sdk/literal/src/Reference/Types/NestedObjectWithLiterals.php index ba14c7c6eae..df74f3540e8 100644 --- a/seed/php-sdk/literal/src/Reference/Types/NestedObjectWithLiterals.php +++ b/seed/php-sdk/literal/src/Reference/Types/NestedObjectWithLiterals.php @@ -2,8 +2,8 @@ namespace Seed\Reference\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedObjectWithLiterals extends SerializableType { diff --git a/seed/php-sdk/literal/src/Reference/Types/SendRequest.php b/seed/php-sdk/literal/src/Reference/Types/SendRequest.php index 4a295d97ee2..7ad6b1c2f6c 100644 --- a/seed/php-sdk/literal/src/Reference/Types/SendRequest.php +++ b/seed/php-sdk/literal/src/Reference/Types/SendRequest.php @@ -2,8 +2,8 @@ namespace Seed\Reference\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SendRequest extends SerializableType { diff --git a/seed/php-sdk/literal/src/SeedClient.php b/seed/php-sdk/literal/src/SeedClient.php index 7b9e878f288..5df45087640 100644 --- a/seed/php-sdk/literal/src/SeedClient.php +++ b/seed/php-sdk/literal/src/SeedClient.php @@ -8,7 +8,7 @@ use Seed\Query\QueryClient; use Seed\Reference\ReferenceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/literal/src/Types/SendResponse.php b/seed/php-sdk/literal/src/Types/SendResponse.php index cd60e9a3795..ae8fc317e87 100644 --- a/seed/php-sdk/literal/src/Types/SendResponse.php +++ b/seed/php-sdk/literal/src/Types/SendResponse.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SendResponse extends SerializableType { diff --git a/seed/php-sdk/literal/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/literal/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/literal/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/EnumTest.php b/seed/php-sdk/literal/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/literal/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/literal/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/literal/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/literal/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/literal/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/literal/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/literal/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/literal/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/ArrayType.php b/seed/php-sdk/mixed-case/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/BaseApiRequest.php b/seed/php-sdk/mixed-case/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/mixed-case/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Client/HttpMethod.php b/seed/php-sdk/mixed-case/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Constant.php b/seed/php-sdk/mixed-case/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Json/JsonDecoder.php b/seed/php-sdk/mixed-case/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/mixed-case/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Json/JsonEncoder.php b/seed/php-sdk/mixed-case/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Json/SerializableType.php b/seed/php-sdk/mixed-case/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Json/Utils.php b/seed/php-sdk/mixed-case/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/JsonApiRequest.php b/seed/php-sdk/mixed-case/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/JsonDecoder.php b/seed/php-sdk/mixed-case/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/JsonDeserializer.php b/seed/php-sdk/mixed-case/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/JsonEncoder.php b/seed/php-sdk/mixed-case/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/RawClient.php b/seed/php-sdk/mixed-case/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/SerializableType.php b/seed/php-sdk/mixed-case/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/Types/ArrayType.php b/seed/php-sdk/mixed-case/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Types/Constant.php b/seed/php-sdk/mixed-case/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/mixed-case/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/mixed-case/src/Core/Union.php b/seed/php-sdk/mixed-case/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/mixed-case/src/Core/Utils.php b/seed/php-sdk/mixed-case/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/mixed-case/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/mixed-case/src/SeedClient.php b/seed/php-sdk/mixed-case/src/SeedClient.php index 5f052555c44..4ca47b660f7 100644 --- a/seed/php-sdk/mixed-case/src/SeedClient.php +++ b/seed/php-sdk/mixed-case/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/mixed-case/src/Service/Requests/ListResourcesRequest.php b/seed/php-sdk/mixed-case/src/Service/Requests/ListResourcesRequest.php index cfe309589ab..d68ac0f3601 100644 --- a/seed/php-sdk/mixed-case/src/Service/Requests/ListResourcesRequest.php +++ b/seed/php-sdk/mixed-case/src/Service/Requests/ListResourcesRequest.php @@ -2,7 +2,7 @@ namespace Seed\Service\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; class ListResourcesRequest extends SerializableType diff --git a/seed/php-sdk/mixed-case/src/Service/ServiceClient.php b/seed/php-sdk/mixed-case/src/Service/ServiceClient.php index ce389719925..c277dea1825 100644 --- a/seed/php-sdk/mixed-case/src/Service/ServiceClient.php +++ b/seed/php-sdk/mixed-case/src/Service/ServiceClient.php @@ -2,16 +2,16 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Service\Requests\ListResourcesRequest; -use Seed\Core\Constant; +use Seed\Core\Types\Constant; class ServiceClient { diff --git a/seed/php-sdk/mixed-case/src/Service/Types/NestedUser.php b/seed/php-sdk/mixed-case/src/Service/Types/NestedUser.php index 4e676789bbd..5dc6ee2479d 100644 --- a/seed/php-sdk/mixed-case/src/Service/Types/NestedUser.php +++ b/seed/php-sdk/mixed-case/src/Service/Types/NestedUser.php @@ -2,8 +2,8 @@ namespace Seed\Service\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedUser extends SerializableType { diff --git a/seed/php-sdk/mixed-case/src/Service/Types/Organization.php b/seed/php-sdk/mixed-case/src/Service/Types/Organization.php index c19568b082f..a0dbacc6a3b 100644 --- a/seed/php-sdk/mixed-case/src/Service/Types/Organization.php +++ b/seed/php-sdk/mixed-case/src/Service/Types/Organization.php @@ -2,8 +2,8 @@ namespace Seed\Service\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Organization extends SerializableType { diff --git a/seed/php-sdk/mixed-case/src/Service/Types/User.php b/seed/php-sdk/mixed-case/src/Service/Types/User.php index 4c325958ef4..f4e769229ba 100644 --- a/seed/php-sdk/mixed-case/src/Service/Types/User.php +++ b/seed/php-sdk/mixed-case/src/Service/Types/User.php @@ -2,9 +2,9 @@ namespace Seed\Service\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class User extends SerializableType { diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/EnumTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/mixed-case/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/mixed-case/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/ArrayType.php b/seed/php-sdk/mixed-file-directory/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/BaseApiRequest.php b/seed/php-sdk/mixed-file-directory/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/mixed-file-directory/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Client/HttpMethod.php b/seed/php-sdk/mixed-file-directory/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Constant.php b/seed/php-sdk/mixed-file-directory/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonDecoder.php b/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonEncoder.php b/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Json/SerializableType.php b/seed/php-sdk/mixed-file-directory/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Json/Utils.php b/seed/php-sdk/mixed-file-directory/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/JsonApiRequest.php b/seed/php-sdk/mixed-file-directory/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/JsonDecoder.php b/seed/php-sdk/mixed-file-directory/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/JsonDeserializer.php b/seed/php-sdk/mixed-file-directory/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/JsonEncoder.php b/seed/php-sdk/mixed-file-directory/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/RawClient.php b/seed/php-sdk/mixed-file-directory/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/SerializableType.php b/seed/php-sdk/mixed-file-directory/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Types/ArrayType.php b/seed/php-sdk/mixed-file-directory/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Types/Constant.php b/seed/php-sdk/mixed-file-directory/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Union.php b/seed/php-sdk/mixed-file-directory/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Core/Utils.php b/seed/php-sdk/mixed-file-directory/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/mixed-file-directory/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/mixed-file-directory/src/Organization/OrganizationClient.php b/seed/php-sdk/mixed-file-directory/src/Organization/OrganizationClient.php index 81ede4e62c3..56480e47a3a 100644 --- a/seed/php-sdk/mixed-file-directory/src/Organization/OrganizationClient.php +++ b/seed/php-sdk/mixed-file-directory/src/Organization/OrganizationClient.php @@ -2,13 +2,13 @@ namespace Seed\Organization; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Organization\Types\CreateOrganizationRequest; use Seed\Organization\Types\Organization; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/mixed-file-directory/src/Organization/Types/CreateOrganizationRequest.php b/seed/php-sdk/mixed-file-directory/src/Organization/Types/CreateOrganizationRequest.php index 48d8fc26506..f1e1e493880 100644 --- a/seed/php-sdk/mixed-file-directory/src/Organization/Types/CreateOrganizationRequest.php +++ b/seed/php-sdk/mixed-file-directory/src/Organization/Types/CreateOrganizationRequest.php @@ -2,8 +2,8 @@ namespace Seed\Organization\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CreateOrganizationRequest extends SerializableType { diff --git a/seed/php-sdk/mixed-file-directory/src/Organization/Types/Organization.php b/seed/php-sdk/mixed-file-directory/src/Organization/Types/Organization.php index 3d7d9a54498..f1c6f468d17 100644 --- a/seed/php-sdk/mixed-file-directory/src/Organization/Types/Organization.php +++ b/seed/php-sdk/mixed-file-directory/src/Organization/Types/Organization.php @@ -2,10 +2,10 @@ namespace Seed\Organization\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\User\Types\User; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class Organization extends SerializableType { diff --git a/seed/php-sdk/mixed-file-directory/src/SeedClient.php b/seed/php-sdk/mixed-file-directory/src/SeedClient.php index c4319a4d001..e1c45fd32b4 100644 --- a/seed/php-sdk/mixed-file-directory/src/SeedClient.php +++ b/seed/php-sdk/mixed-file-directory/src/SeedClient.php @@ -5,7 +5,7 @@ use Seed\Organization\OrganizationClient; use Seed\User\UserClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/mixed-file-directory/src/User/Events/EventsClient.php b/seed/php-sdk/mixed-file-directory/src/User/Events/EventsClient.php index dc99c199956..ea3c500442c 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/Events/EventsClient.php +++ b/seed/php-sdk/mixed-file-directory/src/User/Events/EventsClient.php @@ -3,14 +3,14 @@ namespace Seed\User\Events; use Seed\User\Events\Metadata\MetadataClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\User\Events\Requests\ListUserEventsRequest; use Seed\User\Events\Types\Event; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/MetadataClient.php b/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/MetadataClient.php index ff50679c9b4..52d79346220 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/MetadataClient.php +++ b/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/MetadataClient.php @@ -2,13 +2,13 @@ namespace Seed\User\Events\Metadata; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\User\Events\Metadata\Requests\GetEventMetadataRequest; use Seed\User\Events\Metadata\Types\Metadata; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/Requests/GetEventMetadataRequest.php b/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/Requests/GetEventMetadataRequest.php index 5eaa323d09b..80d1781a077 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/Requests/GetEventMetadataRequest.php +++ b/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/Requests/GetEventMetadataRequest.php @@ -2,7 +2,7 @@ namespace Seed\User\Events\Metadata\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetEventMetadataRequest extends SerializableType { diff --git a/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/Types/Metadata.php b/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/Types/Metadata.php index 8e368785f49..cea05095c74 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/Types/Metadata.php +++ b/seed/php-sdk/mixed-file-directory/src/User/Events/Metadata/Types/Metadata.php @@ -2,8 +2,8 @@ namespace Seed\User\Events\Metadata\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Metadata extends SerializableType { diff --git a/seed/php-sdk/mixed-file-directory/src/User/Events/Requests/ListUserEventsRequest.php b/seed/php-sdk/mixed-file-directory/src/User/Events/Requests/ListUserEventsRequest.php index 6d03f8a8efe..afce7634f15 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/Events/Requests/ListUserEventsRequest.php +++ b/seed/php-sdk/mixed-file-directory/src/User/Events/Requests/ListUserEventsRequest.php @@ -2,7 +2,7 @@ namespace Seed\User\Events\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ListUserEventsRequest extends SerializableType { diff --git a/seed/php-sdk/mixed-file-directory/src/User/Events/Types/Event.php b/seed/php-sdk/mixed-file-directory/src/User/Events/Types/Event.php index 1c07b0985d5..3075f113999 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/Events/Types/Event.php +++ b/seed/php-sdk/mixed-file-directory/src/User/Events/Types/Event.php @@ -2,8 +2,8 @@ namespace Seed\User\Events\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Event extends SerializableType { diff --git a/seed/php-sdk/mixed-file-directory/src/User/Requests/ListUsersRequest.php b/seed/php-sdk/mixed-file-directory/src/User/Requests/ListUsersRequest.php index 5c1bfd95156..35c4aec63b5 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/Requests/ListUsersRequest.php +++ b/seed/php-sdk/mixed-file-directory/src/User/Requests/ListUsersRequest.php @@ -2,7 +2,7 @@ namespace Seed\User\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ListUsersRequest extends SerializableType { diff --git a/seed/php-sdk/mixed-file-directory/src/User/Types/User.php b/seed/php-sdk/mixed-file-directory/src/User/Types/User.php index b74809b77ba..48df168153e 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/Types/User.php +++ b/seed/php-sdk/mixed-file-directory/src/User/Types/User.php @@ -2,8 +2,8 @@ namespace Seed\User\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-sdk/mixed-file-directory/src/User/UserClient.php b/seed/php-sdk/mixed-file-directory/src/User/UserClient.php index 410ab1cbacc..bf59a4fb151 100644 --- a/seed/php-sdk/mixed-file-directory/src/User/UserClient.php +++ b/seed/php-sdk/mixed-file-directory/src/User/UserClient.php @@ -3,14 +3,14 @@ namespace Seed\User; use Seed\User\Events\EventsClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\User\Requests\ListUsersRequest; use Seed\User\Types\User; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/EnumTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/mixed-file-directory/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/ArrayType.php b/seed/php-sdk/multi-line-docs/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/BaseApiRequest.php b/seed/php-sdk/multi-line-docs/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/multi-line-docs/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Client/HttpMethod.php b/seed/php-sdk/multi-line-docs/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Constant.php b/seed/php-sdk/multi-line-docs/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Json/JsonDecoder.php b/seed/php-sdk/multi-line-docs/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/multi-line-docs/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Json/JsonEncoder.php b/seed/php-sdk/multi-line-docs/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Json/SerializableType.php b/seed/php-sdk/multi-line-docs/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Json/Utils.php b/seed/php-sdk/multi-line-docs/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/JsonApiRequest.php b/seed/php-sdk/multi-line-docs/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/JsonDecoder.php b/seed/php-sdk/multi-line-docs/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/JsonDeserializer.php b/seed/php-sdk/multi-line-docs/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/JsonEncoder.php b/seed/php-sdk/multi-line-docs/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/RawClient.php b/seed/php-sdk/multi-line-docs/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/SerializableType.php b/seed/php-sdk/multi-line-docs/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Types/ArrayType.php b/seed/php-sdk/multi-line-docs/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Types/Constant.php b/seed/php-sdk/multi-line-docs/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/multi-line-docs/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Union.php b/seed/php-sdk/multi-line-docs/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/multi-line-docs/src/Core/Utils.php b/seed/php-sdk/multi-line-docs/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/multi-line-docs/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/multi-line-docs/src/SeedClient.php b/seed/php-sdk/multi-line-docs/src/SeedClient.php index be7d76e3425..b69d9150d63 100644 --- a/seed/php-sdk/multi-line-docs/src/SeedClient.php +++ b/seed/php-sdk/multi-line-docs/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\User\UserClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/multi-line-docs/src/User/Requests/CreateUserRequest.php b/seed/php-sdk/multi-line-docs/src/User/Requests/CreateUserRequest.php index 40abe700702..c1d7e7cb1ff 100644 --- a/seed/php-sdk/multi-line-docs/src/User/Requests/CreateUserRequest.php +++ b/seed/php-sdk/multi-line-docs/src/User/Requests/CreateUserRequest.php @@ -2,8 +2,8 @@ namespace Seed\User\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CreateUserRequest extends SerializableType { diff --git a/seed/php-sdk/multi-line-docs/src/User/Types/User.php b/seed/php-sdk/multi-line-docs/src/User/Types/User.php index 936d9e9292c..a708de03bd7 100644 --- a/seed/php-sdk/multi-line-docs/src/User/Types/User.php +++ b/seed/php-sdk/multi-line-docs/src/User/Types/User.php @@ -2,8 +2,8 @@ namespace Seed\User\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * A user object. This type is used throughout the following APIs: diff --git a/seed/php-sdk/multi-line-docs/src/User/UserClient.php b/seed/php-sdk/multi-line-docs/src/User/UserClient.php index 7f9238ff4da..4e461c6237c 100644 --- a/seed/php-sdk/multi-line-docs/src/User/UserClient.php +++ b/seed/php-sdk/multi-line-docs/src/User/UserClient.php @@ -2,11 +2,11 @@ namespace Seed\User; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; use Seed\User\Requests\CreateUserRequest; use Seed\User\Types\User; diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/EnumTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/multi-line-docs/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/no-environment/src/Core/ArrayType.php b/seed/php-sdk/no-environment/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/no-environment/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/no-environment/src/Core/BaseApiRequest.php b/seed/php-sdk/no-environment/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/no-environment/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/no-environment/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/no-environment/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Client/HttpMethod.php b/seed/php-sdk/no-environment/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Constant.php b/seed/php-sdk/no-environment/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/no-environment/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Json/JsonDecoder.php b/seed/php-sdk/no-environment/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/no-environment/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Json/JsonEncoder.php b/seed/php-sdk/no-environment/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Json/SerializableType.php b/seed/php-sdk/no-environment/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Json/Utils.php b/seed/php-sdk/no-environment/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/no-environment/src/Core/JsonApiRequest.php b/seed/php-sdk/no-environment/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/no-environment/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/no-environment/src/Core/JsonDecoder.php b/seed/php-sdk/no-environment/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/no-environment/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/no-environment/src/Core/JsonDeserializer.php b/seed/php-sdk/no-environment/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/no-environment/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/no-environment/src/Core/JsonEncoder.php b/seed/php-sdk/no-environment/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/no-environment/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/no-environment/src/Core/RawClient.php b/seed/php-sdk/no-environment/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/no-environment/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/no-environment/src/Core/SerializableType.php b/seed/php-sdk/no-environment/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/no-environment/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/no-environment/src/Core/Types/ArrayType.php b/seed/php-sdk/no-environment/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Types/Constant.php b/seed/php-sdk/no-environment/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/no-environment/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/no-environment/src/Core/Union.php b/seed/php-sdk/no-environment/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/no-environment/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/no-environment/src/Core/Utils.php b/seed/php-sdk/no-environment/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/no-environment/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/no-environment/src/Dummy/DummyClient.php b/seed/php-sdk/no-environment/src/Dummy/DummyClient.php index ed50edb09f8..28628ee2c15 100644 --- a/seed/php-sdk/no-environment/src/Dummy/DummyClient.php +++ b/seed/php-sdk/no-environment/src/Dummy/DummyClient.php @@ -2,12 +2,12 @@ namespace Seed\Dummy; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/no-environment/src/SeedClient.php b/seed/php-sdk/no-environment/src/SeedClient.php index 49b43015ebb..af08670afc6 100644 --- a/seed/php-sdk/no-environment/src/SeedClient.php +++ b/seed/php-sdk/no-environment/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Dummy\DummyClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/EnumTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/no-environment/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/no-environment/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Auth/AuthClient.php b/seed/php-sdk/oauth-client-credentials-default/src/Auth/AuthClient.php index 7e6187de604..73ac83c5e41 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Auth/AuthClient.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Auth/AuthClient.php @@ -2,13 +2,13 @@ namespace Seed\Auth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Auth\Requests\GetTokenRequest; use Seed\Auth\Types\TokenResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Auth/Requests/GetTokenRequest.php b/seed/php-sdk/oauth-client-credentials-default/src/Auth/Requests/GetTokenRequest.php index dddd94039d1..d8155f140c3 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Auth/Requests/GetTokenRequest.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Auth/Requests/GetTokenRequest.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetTokenRequest extends SerializableType { diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Auth/Types/TokenResponse.php b/seed/php-sdk/oauth-client-credentials-default/src/Auth/Types/TokenResponse.php index 0d8f1db3a9f..31d5bb5c717 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/Auth/Types/TokenResponse.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/Auth/Types/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/ArrayType.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/BaseApiRequest.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/HttpMethod.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Constant.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonEncoder.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/SerializableType.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/Utils.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonApiRequest.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonEncoder.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/RawClient.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/SerializableType.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Types/ArrayType.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Types/Constant.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Union.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/Core/Utils.php b/seed/php-sdk/oauth-client-credentials-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/src/SeedClient.php b/seed/php-sdk/oauth-client-credentials-default/src/SeedClient.php index 6c77b669535..57bb637f506 100644 --- a/seed/php-sdk/oauth-client-credentials-default/src/SeedClient.php +++ b/seed/php-sdk/oauth-client-credentials-default/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Auth\AuthClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/EnumTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/AuthClient.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/AuthClient.php index 6635237675d..4cd7d062dff 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/AuthClient.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/AuthClient.php @@ -2,13 +2,13 @@ namespace Seed\Auth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Auth\Requests\GetTokenRequest; use Seed\Auth\Types\TokenResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Auth\Requests\RefreshTokenRequest; diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Requests/GetTokenRequest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Requests/GetTokenRequest.php index b1536a44943..db7faf22c19 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Requests/GetTokenRequest.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Requests/GetTokenRequest.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetTokenRequest extends SerializableType { diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Requests/RefreshTokenRequest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Requests/RefreshTokenRequest.php index 41522d6b207..c1aad26bfea 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Requests/RefreshTokenRequest.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Requests/RefreshTokenRequest.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RefreshTokenRequest extends SerializableType { diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Types/TokenResponse.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Types/TokenResponse.php index f4dd25eedf1..b1eeb7a9e98 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Types/TokenResponse.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Auth/Types/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/ArrayType.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/BaseApiRequest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/HttpMethod.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Constant.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonEncoder.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/SerializableType.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/Utils.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonApiRequest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonEncoder.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/RawClient.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/SerializableType.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/ArrayType.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/Constant.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Union.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Utils.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/src/SeedClient.php b/seed/php-sdk/oauth-client-credentials-environment-variables/src/SeedClient.php index 6c77b669535..57bb637f506 100644 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/src/SeedClient.php +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Auth\AuthClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/EnumTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/AuthClient.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/AuthClient.php index 7e6187de604..73ac83c5e41 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/AuthClient.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/AuthClient.php @@ -2,13 +2,13 @@ namespace Seed\Auth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Auth\Requests\GetTokenRequest; use Seed\Auth\Types\TokenResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/Requests/GetTokenRequest.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/Requests/GetTokenRequest.php index b1536a44943..db7faf22c19 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/Requests/GetTokenRequest.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/Requests/GetTokenRequest.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetTokenRequest extends SerializableType { diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/Types/TokenResponse.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/Types/TokenResponse.php index f4dd25eedf1..b1eeb7a9e98 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/Types/TokenResponse.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Auth/Types/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/ArrayType.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/BaseApiRequest.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/HttpMethod.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Constant.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonEncoder.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/SerializableType.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/Utils.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonApiRequest.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonEncoder.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/RawClient.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/SerializableType.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/ArrayType.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/Constant.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Union.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Utils.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/src/SeedClient.php b/seed/php-sdk/oauth-client-credentials-nested-root/src/SeedClient.php index 6c77b669535..57bb637f506 100644 --- a/seed/php-sdk/oauth-client-credentials-nested-root/src/SeedClient.php +++ b/seed/php-sdk/oauth-client-credentials-nested-root/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Auth\AuthClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/EnumTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Auth/AuthClient.php b/seed/php-sdk/oauth-client-credentials/src/Auth/AuthClient.php index 6635237675d..4cd7d062dff 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Auth/AuthClient.php +++ b/seed/php-sdk/oauth-client-credentials/src/Auth/AuthClient.php @@ -2,13 +2,13 @@ namespace Seed\Auth; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Auth\Requests\GetTokenRequest; use Seed\Auth\Types\TokenResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Auth\Requests\RefreshTokenRequest; diff --git a/seed/php-sdk/oauth-client-credentials/src/Auth/Requests/GetTokenRequest.php b/seed/php-sdk/oauth-client-credentials/src/Auth/Requests/GetTokenRequest.php index b1536a44943..db7faf22c19 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Auth/Requests/GetTokenRequest.php +++ b/seed/php-sdk/oauth-client-credentials/src/Auth/Requests/GetTokenRequest.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetTokenRequest extends SerializableType { diff --git a/seed/php-sdk/oauth-client-credentials/src/Auth/Requests/RefreshTokenRequest.php b/seed/php-sdk/oauth-client-credentials/src/Auth/Requests/RefreshTokenRequest.php index 41522d6b207..c1aad26bfea 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Auth/Requests/RefreshTokenRequest.php +++ b/seed/php-sdk/oauth-client-credentials/src/Auth/Requests/RefreshTokenRequest.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RefreshTokenRequest extends SerializableType { diff --git a/seed/php-sdk/oauth-client-credentials/src/Auth/Types/TokenResponse.php b/seed/php-sdk/oauth-client-credentials/src/Auth/Types/TokenResponse.php index f4dd25eedf1..b1eeb7a9e98 100644 --- a/seed/php-sdk/oauth-client-credentials/src/Auth/Types/TokenResponse.php +++ b/seed/php-sdk/oauth-client-credentials/src/Auth/Types/TokenResponse.php @@ -2,8 +2,8 @@ namespace Seed\Auth\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * An OAuth token response. diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/ArrayType.php b/seed/php-sdk/oauth-client-credentials/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/BaseApiRequest.php b/seed/php-sdk/oauth-client-credentials/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/oauth-client-credentials/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Client/HttpMethod.php b/seed/php-sdk/oauth-client-credentials/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Constant.php b/seed/php-sdk/oauth-client-credentials/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonEncoder.php b/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Json/SerializableType.php b/seed/php-sdk/oauth-client-credentials/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Json/Utils.php b/seed/php-sdk/oauth-client-credentials/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/JsonApiRequest.php b/seed/php-sdk/oauth-client-credentials/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/JsonDecoder.php b/seed/php-sdk/oauth-client-credentials/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/JsonDeserializer.php b/seed/php-sdk/oauth-client-credentials/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/JsonEncoder.php b/seed/php-sdk/oauth-client-credentials/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/RawClient.php b/seed/php-sdk/oauth-client-credentials/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/SerializableType.php b/seed/php-sdk/oauth-client-credentials/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Types/ArrayType.php b/seed/php-sdk/oauth-client-credentials/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Types/Constant.php b/seed/php-sdk/oauth-client-credentials/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Union.php b/seed/php-sdk/oauth-client-credentials/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/Core/Utils.php b/seed/php-sdk/oauth-client-credentials/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/oauth-client-credentials/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/oauth-client-credentials/src/SeedClient.php b/seed/php-sdk/oauth-client-credentials/src/SeedClient.php index 6c77b669535..57bb637f506 100644 --- a/seed/php-sdk/oauth-client-credentials/src/SeedClient.php +++ b/seed/php-sdk/oauth-client-credentials/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Auth\AuthClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/EnumTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/oauth-client-credentials/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/object/src/Core/ArrayType.php b/seed/php-sdk/object/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/object/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/object/src/Core/BaseApiRequest.php b/seed/php-sdk/object/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/object/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/object/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/object/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/object/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/object/src/Core/Client/HttpMethod.php b/seed/php-sdk/object/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/object/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/object/src/Core/Constant.php b/seed/php-sdk/object/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/object/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/object/src/Core/Json/JsonDecoder.php b/seed/php-sdk/object/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/object/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/object/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/object/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/object/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/object/src/Core/Json/JsonEncoder.php b/seed/php-sdk/object/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/object/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/object/src/Core/Json/SerializableType.php b/seed/php-sdk/object/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/object/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/object/src/Core/Json/Utils.php b/seed/php-sdk/object/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/object/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/object/src/Core/JsonApiRequest.php b/seed/php-sdk/object/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/object/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/object/src/Core/JsonDecoder.php b/seed/php-sdk/object/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/object/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/object/src/Core/JsonDeserializer.php b/seed/php-sdk/object/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/object/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/object/src/Core/JsonEncoder.php b/seed/php-sdk/object/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/object/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/object/src/Core/RawClient.php b/seed/php-sdk/object/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/object/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/object/src/Core/SerializableType.php b/seed/php-sdk/object/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/object/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/object/src/Core/Types/ArrayType.php b/seed/php-sdk/object/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/object/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/object/src/Core/Types/Constant.php b/seed/php-sdk/object/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/object/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/object/src/Core/Union.php b/seed/php-sdk/object/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/object/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/object/src/Core/Utils.php b/seed/php-sdk/object/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/object/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/object/src/Types/Name.php b/seed/php-sdk/object/src/Types/Name.php index fbc5051aee4..1b2692627a3 100644 --- a/seed/php-sdk/object/src/Types/Name.php +++ b/seed/php-sdk/object/src/Types/Name.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Name extends SerializableType { diff --git a/seed/php-sdk/object/src/Types/Type.php b/seed/php-sdk/object/src/Types/Type.php index bd858580ea2..5dabe8b847c 100644 --- a/seed/php-sdk/object/src/Types/Type.php +++ b/seed/php-sdk/object/src/Types/Type.php @@ -2,12 +2,12 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use DateTime; -use Seed\Core\DateType; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Types\DateType; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; /** * Exercises all of the built-in types. diff --git a/seed/php-sdk/object/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/object/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/object/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/EnumTest.php b/seed/php-sdk/object/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/object/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/object/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/object/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/object/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/object/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/object/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/object/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/object/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Commons/Metadata/Types/Metadata.php b/seed/php-sdk/objects-with-imports/src/Commons/Metadata/Types/Metadata.php index 18acd8c7b4d..87fe57a8b4f 100644 --- a/seed/php-sdk/objects-with-imports/src/Commons/Metadata/Types/Metadata.php +++ b/seed/php-sdk/objects-with-imports/src/Commons/Metadata/Types/Metadata.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Metadata\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Metadata extends SerializableType { diff --git a/seed/php-sdk/objects-with-imports/src/Core/ArrayType.php b/seed/php-sdk/objects-with-imports/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/BaseApiRequest.php b/seed/php-sdk/objects-with-imports/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/objects-with-imports/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Client/HttpMethod.php b/seed/php-sdk/objects-with-imports/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Constant.php b/seed/php-sdk/objects-with-imports/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Json/JsonDecoder.php b/seed/php-sdk/objects-with-imports/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/objects-with-imports/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Json/JsonEncoder.php b/seed/php-sdk/objects-with-imports/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Json/SerializableType.php b/seed/php-sdk/objects-with-imports/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Json/Utils.php b/seed/php-sdk/objects-with-imports/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/JsonApiRequest.php b/seed/php-sdk/objects-with-imports/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/JsonDecoder.php b/seed/php-sdk/objects-with-imports/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/JsonDeserializer.php b/seed/php-sdk/objects-with-imports/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/JsonEncoder.php b/seed/php-sdk/objects-with-imports/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/RawClient.php b/seed/php-sdk/objects-with-imports/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/SerializableType.php b/seed/php-sdk/objects-with-imports/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Types/ArrayType.php b/seed/php-sdk/objects-with-imports/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Types/Constant.php b/seed/php-sdk/objects-with-imports/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/objects-with-imports/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Union.php b/seed/php-sdk/objects-with-imports/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/objects-with-imports/src/Core/Utils.php b/seed/php-sdk/objects-with-imports/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/objects-with-imports/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/objects-with-imports/src/File/Directory/Types/Directory.php b/seed/php-sdk/objects-with-imports/src/File/Directory/Types/Directory.php index 57a48f3d74a..513d07a4d0d 100644 --- a/seed/php-sdk/objects-with-imports/src/File/Directory/Types/Directory.php +++ b/seed/php-sdk/objects-with-imports/src/File/Directory/Types/Directory.php @@ -2,10 +2,10 @@ namespace Seed\File\Directory\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\File\Types\File; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class Directory extends SerializableType { diff --git a/seed/php-sdk/objects-with-imports/src/File/Types/File.php b/seed/php-sdk/objects-with-imports/src/File/Types/File.php index b711fbd7a46..ed08d6f44a9 100644 --- a/seed/php-sdk/objects-with-imports/src/File/Types/File.php +++ b/seed/php-sdk/objects-with-imports/src/File/Types/File.php @@ -2,8 +2,8 @@ namespace Seed\File\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class File extends SerializableType { diff --git a/seed/php-sdk/objects-with-imports/src/Types/Node.php b/seed/php-sdk/objects-with-imports/src/Types/Node.php index 4781de4bc7f..869cdfff56b 100644 --- a/seed/php-sdk/objects-with-imports/src/Types/Node.php +++ b/seed/php-sdk/objects-with-imports/src/Types/Node.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Metadata\Types\Metadata; class Node extends SerializableType diff --git a/seed/php-sdk/objects-with-imports/src/Types/Tree.php b/seed/php-sdk/objects-with-imports/src/Types/Tree.php index 5635dbaef47..4d1ad0ebb5e 100644 --- a/seed/php-sdk/objects-with-imports/src/Types/Tree.php +++ b/seed/php-sdk/objects-with-imports/src/Types/Tree.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Tree extends SerializableType { diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/EnumTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/objects-with-imports/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/optional/src/Core/ArrayType.php b/seed/php-sdk/optional/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/optional/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/optional/src/Core/BaseApiRequest.php b/seed/php-sdk/optional/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/optional/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/optional/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/optional/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/optional/src/Core/Client/HttpMethod.php b/seed/php-sdk/optional/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/optional/src/Core/Constant.php b/seed/php-sdk/optional/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/optional/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/optional/src/Core/Json/JsonDecoder.php b/seed/php-sdk/optional/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/optional/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/optional/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/optional/src/Core/Json/JsonEncoder.php b/seed/php-sdk/optional/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/optional/src/Core/Json/SerializableType.php b/seed/php-sdk/optional/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/optional/src/Core/Json/Utils.php b/seed/php-sdk/optional/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/optional/src/Core/JsonApiRequest.php b/seed/php-sdk/optional/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/optional/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/optional/src/Core/JsonDecoder.php b/seed/php-sdk/optional/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/optional/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/optional/src/Core/JsonDeserializer.php b/seed/php-sdk/optional/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/optional/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/optional/src/Core/JsonEncoder.php b/seed/php-sdk/optional/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/optional/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/optional/src/Core/RawClient.php b/seed/php-sdk/optional/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/optional/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/optional/src/Core/SerializableType.php b/seed/php-sdk/optional/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/optional/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/optional/src/Core/Types/ArrayType.php b/seed/php-sdk/optional/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/optional/src/Core/Types/Constant.php b/seed/php-sdk/optional/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/optional/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/optional/src/Core/Union.php b/seed/php-sdk/optional/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/optional/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/optional/src/Core/Utils.php b/seed/php-sdk/optional/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/optional/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/optional/src/Optional/OptionalClient.php b/seed/php-sdk/optional/src/Optional/OptionalClient.php index 7341a435e78..66a0e7da948 100644 --- a/seed/php-sdk/optional/src/Optional/OptionalClient.php +++ b/seed/php-sdk/optional/src/Optional/OptionalClient.php @@ -2,13 +2,13 @@ namespace Seed\Optional; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonSerializer; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonSerializer; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/optional/src/SeedClient.php b/seed/php-sdk/optional/src/SeedClient.php index 53d34891786..c2a18427654 100644 --- a/seed/php-sdk/optional/src/SeedClient.php +++ b/seed/php-sdk/optional/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Optional\OptionalClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/optional/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/optional/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/optional/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/EnumTest.php b/seed/php-sdk/optional/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/optional/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/optional/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/optional/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/optional/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/optional/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/optional/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/optional/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/optional/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/package-yml/src/Core/ArrayType.php b/seed/php-sdk/package-yml/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/package-yml/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/package-yml/src/Core/BaseApiRequest.php b/seed/php-sdk/package-yml/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/package-yml/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/package-yml/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/package-yml/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Client/HttpMethod.php b/seed/php-sdk/package-yml/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Constant.php b/seed/php-sdk/package-yml/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/package-yml/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Json/JsonDecoder.php b/seed/php-sdk/package-yml/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/package-yml/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Json/JsonEncoder.php b/seed/php-sdk/package-yml/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Json/SerializableType.php b/seed/php-sdk/package-yml/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Json/Utils.php b/seed/php-sdk/package-yml/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/package-yml/src/Core/JsonApiRequest.php b/seed/php-sdk/package-yml/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/package-yml/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/package-yml/src/Core/JsonDecoder.php b/seed/php-sdk/package-yml/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/package-yml/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/package-yml/src/Core/JsonDeserializer.php b/seed/php-sdk/package-yml/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/package-yml/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/package-yml/src/Core/JsonEncoder.php b/seed/php-sdk/package-yml/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/package-yml/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/package-yml/src/Core/RawClient.php b/seed/php-sdk/package-yml/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/package-yml/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/package-yml/src/Core/SerializableType.php b/seed/php-sdk/package-yml/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/package-yml/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/package-yml/src/Core/Types/ArrayType.php b/seed/php-sdk/package-yml/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Types/Constant.php b/seed/php-sdk/package-yml/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/package-yml/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/package-yml/src/Core/Union.php b/seed/php-sdk/package-yml/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/package-yml/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/package-yml/src/Core/Utils.php b/seed/php-sdk/package-yml/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/package-yml/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/package-yml/src/SeedClient.php b/seed/php-sdk/package-yml/src/SeedClient.php index 8d6cc70dda4..653a3af7976 100644 --- a/seed/php-sdk/package-yml/src/SeedClient.php +++ b/seed/php-sdk/package-yml/src/SeedClient.php @@ -4,13 +4,13 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Types\EchoRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/package-yml/src/Service/ServiceClient.php b/seed/php-sdk/package-yml/src/Service/ServiceClient.php index 844955701a9..44abb5b417e 100644 --- a/seed/php-sdk/package-yml/src/Service/ServiceClient.php +++ b/seed/php-sdk/package-yml/src/Service/ServiceClient.php @@ -2,11 +2,11 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class ServiceClient diff --git a/seed/php-sdk/package-yml/src/Types/EchoRequest.php b/seed/php-sdk/package-yml/src/Types/EchoRequest.php index 548e75f3df8..b0b574aa0be 100644 --- a/seed/php-sdk/package-yml/src/Types/EchoRequest.php +++ b/seed/php-sdk/package-yml/src/Types/EchoRequest.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class EchoRequest extends SerializableType { diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/EnumTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/package-yml/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/package-yml/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/pagination/src/Core/ArrayType.php b/seed/php-sdk/pagination/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/pagination/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/pagination/src/Core/BaseApiRequest.php b/seed/php-sdk/pagination/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/pagination/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/pagination/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/pagination/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/pagination/src/Core/Client/HttpMethod.php b/seed/php-sdk/pagination/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/pagination/src/Core/Constant.php b/seed/php-sdk/pagination/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/pagination/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/pagination/src/Core/Json/JsonDecoder.php b/seed/php-sdk/pagination/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/pagination/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/pagination/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/pagination/src/Core/Json/JsonEncoder.php b/seed/php-sdk/pagination/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/pagination/src/Core/Json/SerializableType.php b/seed/php-sdk/pagination/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/pagination/src/Core/Json/Utils.php b/seed/php-sdk/pagination/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/pagination/src/Core/JsonApiRequest.php b/seed/php-sdk/pagination/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/pagination/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/pagination/src/Core/JsonDecoder.php b/seed/php-sdk/pagination/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/pagination/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/pagination/src/Core/JsonDeserializer.php b/seed/php-sdk/pagination/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/pagination/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/pagination/src/Core/JsonEncoder.php b/seed/php-sdk/pagination/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/pagination/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/pagination/src/Core/RawClient.php b/seed/php-sdk/pagination/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/pagination/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/pagination/src/Core/SerializableType.php b/seed/php-sdk/pagination/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/pagination/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/pagination/src/Core/Types/ArrayType.php b/seed/php-sdk/pagination/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/pagination/src/Core/Types/Constant.php b/seed/php-sdk/pagination/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/pagination/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/pagination/src/Core/Union.php b/seed/php-sdk/pagination/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/pagination/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/pagination/src/Core/Utils.php b/seed/php-sdk/pagination/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/pagination/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/pagination/src/SeedClient.php b/seed/php-sdk/pagination/src/SeedClient.php index 92bf51b382f..2bfd0110573 100644 --- a/seed/php-sdk/pagination/src/SeedClient.php +++ b/seed/php-sdk/pagination/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Users\UsersClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/pagination/src/Types/UsernameCursor.php b/seed/php-sdk/pagination/src/Types/UsernameCursor.php index 2ef781a07ff..1e0465a17c3 100644 --- a/seed/php-sdk/pagination/src/Types/UsernameCursor.php +++ b/seed/php-sdk/pagination/src/Types/UsernameCursor.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UsernameCursor extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Types/UsernamePage.php b/seed/php-sdk/pagination/src/Types/UsernamePage.php index 7808cc88287..3120b504220 100644 --- a/seed/php-sdk/pagination/src/Types/UsernamePage.php +++ b/seed/php-sdk/pagination/src/Types/UsernamePage.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UsernamePage extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListUsernamesRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListUsernamesRequest.php index 1c2ad8f6a69..d0c45d26e82 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListUsernamesRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListUsernamesRequest.php @@ -2,7 +2,7 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ListUsernamesRequest extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListUsersBodyCursorPaginationRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListUsersBodyCursorPaginationRequest.php index 43be2a2a3f3..54c23edddaf 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListUsersBodyCursorPaginationRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListUsersBodyCursorPaginationRequest.php @@ -2,9 +2,9 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Users\Types\WithCursor; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class ListUsersBodyCursorPaginationRequest extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListUsersBodyOffsetPaginationRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListUsersBodyOffsetPaginationRequest.php index cd00eaae58b..33df48d9d37 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListUsersBodyOffsetPaginationRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListUsersBodyOffsetPaginationRequest.php @@ -2,9 +2,9 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Users\Types\WithPage; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class ListUsersBodyOffsetPaginationRequest extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListUsersCursorPaginationRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListUsersCursorPaginationRequest.php index 5f81459612b..dc2727c9250 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListUsersCursorPaginationRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListUsersCursorPaginationRequest.php @@ -2,7 +2,7 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Users\Types\Order; class ListUsersCursorPaginationRequest extends SerializableType diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListUsersExtendedRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListUsersExtendedRequest.php index 5e8e2f2131c..c1649386fa4 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListUsersExtendedRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListUsersExtendedRequest.php @@ -2,7 +2,7 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ListUsersExtendedRequest extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListUsersExtendedRequestForOptionalData.php b/seed/php-sdk/pagination/src/Users/Requests/ListUsersExtendedRequestForOptionalData.php index 181103aed9e..18d004d1b25 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListUsersExtendedRequestForOptionalData.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListUsersExtendedRequestForOptionalData.php @@ -2,7 +2,7 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ListUsersExtendedRequestForOptionalData extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListUsersOffsetPaginationRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListUsersOffsetPaginationRequest.php index d575ac120a6..d9e9039a8db 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListUsersOffsetPaginationRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListUsersOffsetPaginationRequest.php @@ -2,7 +2,7 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Users\Types\Order; class ListUsersOffsetPaginationRequest extends SerializableType diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListUsersOffsetStepPaginationRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListUsersOffsetStepPaginationRequest.php index ff7118d38bf..4ec6c52054b 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListUsersOffsetStepPaginationRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListUsersOffsetStepPaginationRequest.php @@ -2,7 +2,7 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Users\Types\Order; class ListUsersOffsetStepPaginationRequest extends SerializableType diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListWithGlobalConfigRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListWithGlobalConfigRequest.php index f70b214b891..9b0355da68f 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListWithGlobalConfigRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListWithGlobalConfigRequest.php @@ -2,7 +2,7 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class ListWithGlobalConfigRequest extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Requests/ListWithOffsetPaginationHasNextPageRequest.php b/seed/php-sdk/pagination/src/Users/Requests/ListWithOffsetPaginationHasNextPageRequest.php index c1907261521..1101fd0abee 100644 --- a/seed/php-sdk/pagination/src/Users/Requests/ListWithOffsetPaginationHasNextPageRequest.php +++ b/seed/php-sdk/pagination/src/Users/Requests/ListWithOffsetPaginationHasNextPageRequest.php @@ -2,7 +2,7 @@ namespace Seed\Users\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Users\Types\Order; class ListWithOffsetPaginationHasNextPageRequest extends SerializableType diff --git a/seed/php-sdk/pagination/src/Users/Types/ListUsersExtendedOptionalListResponse.php b/seed/php-sdk/pagination/src/Users/Types/ListUsersExtendedOptionalListResponse.php index 2f943dcad82..5866b827cb1 100644 --- a/seed/php-sdk/pagination/src/Users/Types/ListUsersExtendedOptionalListResponse.php +++ b/seed/php-sdk/pagination/src/Users/Types/ListUsersExtendedOptionalListResponse.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ListUsersExtendedOptionalListResponse extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/ListUsersExtendedResponse.php b/seed/php-sdk/pagination/src/Users/Types/ListUsersExtendedResponse.php index c69cc4fe31c..2629506d6ee 100644 --- a/seed/php-sdk/pagination/src/Users/Types/ListUsersExtendedResponse.php +++ b/seed/php-sdk/pagination/src/Users/Types/ListUsersExtendedResponse.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ListUsersExtendedResponse extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/ListUsersPaginationResponse.php b/seed/php-sdk/pagination/src/Users/Types/ListUsersPaginationResponse.php index 414ee5459d0..52c5fe34c56 100644 --- a/seed/php-sdk/pagination/src/Users/Types/ListUsersPaginationResponse.php +++ b/seed/php-sdk/pagination/src/Users/Types/ListUsersPaginationResponse.php @@ -2,9 +2,9 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ListUsersPaginationResponse extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/NextPage.php b/seed/php-sdk/pagination/src/Users/Types/NextPage.php index 08513c868f3..97075f33e62 100644 --- a/seed/php-sdk/pagination/src/Users/Types/NextPage.php +++ b/seed/php-sdk/pagination/src/Users/Types/NextPage.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NextPage extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/Page.php b/seed/php-sdk/pagination/src/Users/Types/Page.php index 6de84c0835f..3c043d999b8 100644 --- a/seed/php-sdk/pagination/src/Users/Types/Page.php +++ b/seed/php-sdk/pagination/src/Users/Types/Page.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Page extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/User.php b/seed/php-sdk/pagination/src/Users/Types/User.php index 38118fa0c63..5ae88f90ec4 100644 --- a/seed/php-sdk/pagination/src/Users/Types/User.php +++ b/seed/php-sdk/pagination/src/Users/Types/User.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/UserListContainer.php b/seed/php-sdk/pagination/src/Users/Types/UserListContainer.php index d5fc092b30c..4b46db2fb20 100644 --- a/seed/php-sdk/pagination/src/Users/Types/UserListContainer.php +++ b/seed/php-sdk/pagination/src/Users/Types/UserListContainer.php @@ -2,9 +2,9 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UserListContainer extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/UserOptionalListContainer.php b/seed/php-sdk/pagination/src/Users/Types/UserOptionalListContainer.php index afc35652861..d31bd08ee73 100644 --- a/seed/php-sdk/pagination/src/Users/Types/UserOptionalListContainer.php +++ b/seed/php-sdk/pagination/src/Users/Types/UserOptionalListContainer.php @@ -2,9 +2,9 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UserOptionalListContainer extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/UserOptionalListPage.php b/seed/php-sdk/pagination/src/Users/Types/UserOptionalListPage.php index abcb988314b..eeceaccb05a 100644 --- a/seed/php-sdk/pagination/src/Users/Types/UserOptionalListPage.php +++ b/seed/php-sdk/pagination/src/Users/Types/UserOptionalListPage.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UserOptionalListPage extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/UserPage.php b/seed/php-sdk/pagination/src/Users/Types/UserPage.php index f3bd80129ba..fd91f76da4b 100644 --- a/seed/php-sdk/pagination/src/Users/Types/UserPage.php +++ b/seed/php-sdk/pagination/src/Users/Types/UserPage.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UserPage extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/UsernameContainer.php b/seed/php-sdk/pagination/src/Users/Types/UsernameContainer.php index 7f8b36de3d8..39e5c72d4df 100644 --- a/seed/php-sdk/pagination/src/Users/Types/UsernameContainer.php +++ b/seed/php-sdk/pagination/src/Users/Types/UsernameContainer.php @@ -2,9 +2,9 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UsernameContainer extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/WithCursor.php b/seed/php-sdk/pagination/src/Users/Types/WithCursor.php index 21ea45accbb..2a0d208bd9e 100644 --- a/seed/php-sdk/pagination/src/Users/Types/WithCursor.php +++ b/seed/php-sdk/pagination/src/Users/Types/WithCursor.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WithCursor extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/Types/WithPage.php b/seed/php-sdk/pagination/src/Users/Types/WithPage.php index 04efee138a3..8c1449b89b2 100644 --- a/seed/php-sdk/pagination/src/Users/Types/WithPage.php +++ b/seed/php-sdk/pagination/src/Users/Types/WithPage.php @@ -2,8 +2,8 @@ namespace Seed\Users\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WithPage extends SerializableType { diff --git a/seed/php-sdk/pagination/src/Users/UsersClient.php b/seed/php-sdk/pagination/src/Users/UsersClient.php index 401c2403ea3..523e82c1189 100644 --- a/seed/php-sdk/pagination/src/Users/UsersClient.php +++ b/seed/php-sdk/pagination/src/Users/UsersClient.php @@ -2,13 +2,13 @@ namespace Seed\Users; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Users\Requests\ListUsersCursorPaginationRequest; use Seed\Users\Types\ListUsersPaginationResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Users\Requests\ListUsersBodyCursorPaginationRequest; diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/pagination/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/EnumTest.php b/seed/php-sdk/pagination/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/pagination/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/pagination/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/pagination/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/pagination/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/pagination/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/pagination/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/pagination/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/plain-text/src/Core/ArrayType.php b/seed/php-sdk/plain-text/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/plain-text/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/plain-text/src/Core/BaseApiRequest.php b/seed/php-sdk/plain-text/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/plain-text/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/plain-text/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/plain-text/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Client/HttpMethod.php b/seed/php-sdk/plain-text/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Constant.php b/seed/php-sdk/plain-text/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/plain-text/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Json/JsonDecoder.php b/seed/php-sdk/plain-text/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/plain-text/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Json/JsonEncoder.php b/seed/php-sdk/plain-text/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Json/SerializableType.php b/seed/php-sdk/plain-text/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Json/Utils.php b/seed/php-sdk/plain-text/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/plain-text/src/Core/JsonApiRequest.php b/seed/php-sdk/plain-text/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/plain-text/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/plain-text/src/Core/JsonDecoder.php b/seed/php-sdk/plain-text/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/plain-text/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/plain-text/src/Core/JsonDeserializer.php b/seed/php-sdk/plain-text/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/plain-text/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/plain-text/src/Core/JsonEncoder.php b/seed/php-sdk/plain-text/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/plain-text/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/plain-text/src/Core/RawClient.php b/seed/php-sdk/plain-text/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/plain-text/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/plain-text/src/Core/SerializableType.php b/seed/php-sdk/plain-text/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/plain-text/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/plain-text/src/Core/Types/ArrayType.php b/seed/php-sdk/plain-text/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Types/Constant.php b/seed/php-sdk/plain-text/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/plain-text/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/plain-text/src/Core/Union.php b/seed/php-sdk/plain-text/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/plain-text/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/plain-text/src/Core/Utils.php b/seed/php-sdk/plain-text/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/plain-text/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/plain-text/src/SeedClient.php b/seed/php-sdk/plain-text/src/SeedClient.php index 5f052555c44..4ca47b660f7 100644 --- a/seed/php-sdk/plain-text/src/SeedClient.php +++ b/seed/php-sdk/plain-text/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/plain-text/src/Service/ServiceClient.php b/seed/php-sdk/plain-text/src/Service/ServiceClient.php index 3ef74d32968..830c4328190 100644 --- a/seed/php-sdk/plain-text/src/Service/ServiceClient.php +++ b/seed/php-sdk/plain-text/src/Service/ServiceClient.php @@ -2,11 +2,11 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class ServiceClient diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/EnumTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/plain-text/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/plain-text/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/ArrayType.php b/seed/php-sdk/query-parameters/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/BaseApiRequest.php b/seed/php-sdk/query-parameters/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/query-parameters/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Client/HttpMethod.php b/seed/php-sdk/query-parameters/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Constant.php b/seed/php-sdk/query-parameters/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Json/JsonDecoder.php b/seed/php-sdk/query-parameters/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/query-parameters/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Json/JsonEncoder.php b/seed/php-sdk/query-parameters/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Json/SerializableType.php b/seed/php-sdk/query-parameters/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Json/Utils.php b/seed/php-sdk/query-parameters/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/JsonApiRequest.php b/seed/php-sdk/query-parameters/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/JsonDecoder.php b/seed/php-sdk/query-parameters/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/JsonDeserializer.php b/seed/php-sdk/query-parameters/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/JsonEncoder.php b/seed/php-sdk/query-parameters/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/RawClient.php b/seed/php-sdk/query-parameters/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/SerializableType.php b/seed/php-sdk/query-parameters/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/Types/ArrayType.php b/seed/php-sdk/query-parameters/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Types/Constant.php b/seed/php-sdk/query-parameters/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/query-parameters/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/query-parameters/src/Core/Union.php b/seed/php-sdk/query-parameters/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/query-parameters/src/Core/Utils.php b/seed/php-sdk/query-parameters/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/query-parameters/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/query-parameters/src/SeedClient.php b/seed/php-sdk/query-parameters/src/SeedClient.php index be7d76e3425..b69d9150d63 100644 --- a/seed/php-sdk/query-parameters/src/SeedClient.php +++ b/seed/php-sdk/query-parameters/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\User\UserClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/query-parameters/src/User/Requests/GetUsersRequest.php b/seed/php-sdk/query-parameters/src/User/Requests/GetUsersRequest.php index aeeba235a91..68e5c85b6b6 100644 --- a/seed/php-sdk/query-parameters/src/User/Requests/GetUsersRequest.php +++ b/seed/php-sdk/query-parameters/src/User/Requests/GetUsersRequest.php @@ -2,7 +2,7 @@ namespace Seed\User\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; use Seed\User\Types\User; use Seed\User\Types\NestedUser; diff --git a/seed/php-sdk/query-parameters/src/User/Types/NestedUser.php b/seed/php-sdk/query-parameters/src/User/Types/NestedUser.php index eb040d368ab..bf3daa8c38a 100644 --- a/seed/php-sdk/query-parameters/src/User/Types/NestedUser.php +++ b/seed/php-sdk/query-parameters/src/User/Types/NestedUser.php @@ -2,8 +2,8 @@ namespace Seed\User\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NestedUser extends SerializableType { diff --git a/seed/php-sdk/query-parameters/src/User/Types/User.php b/seed/php-sdk/query-parameters/src/User/Types/User.php index eaf76ceb409..4a69eb87110 100644 --- a/seed/php-sdk/query-parameters/src/User/Types/User.php +++ b/seed/php-sdk/query-parameters/src/User/Types/User.php @@ -2,9 +2,9 @@ namespace Seed\User\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class User extends SerializableType { diff --git a/seed/php-sdk/query-parameters/src/User/UserClient.php b/seed/php-sdk/query-parameters/src/User/UserClient.php index 6b5079d2da7..cf523f804bc 100644 --- a/seed/php-sdk/query-parameters/src/User/UserClient.php +++ b/seed/php-sdk/query-parameters/src/User/UserClient.php @@ -2,14 +2,14 @@ namespace Seed\User; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\User\Requests\GetUsersRequest; use Seed\User\Types\User; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\Constant; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Types\Constant; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/EnumTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/query-parameters/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/query-parameters/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/ArrayType.php b/seed/php-sdk/reserved-keywords/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/BaseApiRequest.php b/seed/php-sdk/reserved-keywords/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/reserved-keywords/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Client/HttpMethod.php b/seed/php-sdk/reserved-keywords/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Constant.php b/seed/php-sdk/reserved-keywords/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Json/JsonDecoder.php b/seed/php-sdk/reserved-keywords/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/reserved-keywords/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Json/JsonEncoder.php b/seed/php-sdk/reserved-keywords/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Json/SerializableType.php b/seed/php-sdk/reserved-keywords/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Json/Utils.php b/seed/php-sdk/reserved-keywords/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/JsonApiRequest.php b/seed/php-sdk/reserved-keywords/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/JsonDecoder.php b/seed/php-sdk/reserved-keywords/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/JsonDeserializer.php b/seed/php-sdk/reserved-keywords/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/JsonEncoder.php b/seed/php-sdk/reserved-keywords/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/RawClient.php b/seed/php-sdk/reserved-keywords/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/SerializableType.php b/seed/php-sdk/reserved-keywords/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Types/ArrayType.php b/seed/php-sdk/reserved-keywords/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Types/Constant.php b/seed/php-sdk/reserved-keywords/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/reserved-keywords/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Union.php b/seed/php-sdk/reserved-keywords/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Core/Utils.php b/seed/php-sdk/reserved-keywords/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/reserved-keywords/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/reserved-keywords/src/Package/PackageClient.php b/seed/php-sdk/reserved-keywords/src/Package/PackageClient.php index 21f2edf6b8a..858abcabbfe 100644 --- a/seed/php-sdk/reserved-keywords/src/Package/PackageClient.php +++ b/seed/php-sdk/reserved-keywords/src/Package/PackageClient.php @@ -2,12 +2,12 @@ namespace Seed\Package; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Package\Requests\TestRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class PackageClient diff --git a/seed/php-sdk/reserved-keywords/src/Package/Requests/TestRequest.php b/seed/php-sdk/reserved-keywords/src/Package/Requests/TestRequest.php index c2f988cfea3..177308b421a 100644 --- a/seed/php-sdk/reserved-keywords/src/Package/Requests/TestRequest.php +++ b/seed/php-sdk/reserved-keywords/src/Package/Requests/TestRequest.php @@ -2,7 +2,7 @@ namespace Seed\Package\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class TestRequest extends SerializableType { diff --git a/seed/php-sdk/reserved-keywords/src/Package/Types/Package.php b/seed/php-sdk/reserved-keywords/src/Package/Types/Package.php index 466934e52da..4aab0cf209f 100644 --- a/seed/php-sdk/reserved-keywords/src/Package/Types/Package.php +++ b/seed/php-sdk/reserved-keywords/src/Package/Types/Package.php @@ -2,8 +2,8 @@ namespace Seed\Package\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Package extends SerializableType { diff --git a/seed/php-sdk/reserved-keywords/src/Package/Types/Record.php b/seed/php-sdk/reserved-keywords/src/Package/Types/Record.php index ba89c8d1457..03c6c3a0207 100644 --- a/seed/php-sdk/reserved-keywords/src/Package/Types/Record.php +++ b/seed/php-sdk/reserved-keywords/src/Package/Types/Record.php @@ -2,9 +2,9 @@ namespace Seed\Package\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Record extends SerializableType { diff --git a/seed/php-sdk/reserved-keywords/src/SeedClient.php b/seed/php-sdk/reserved-keywords/src/SeedClient.php index c051954cea5..0a7d2e45927 100644 --- a/seed/php-sdk/reserved-keywords/src/SeedClient.php +++ b/seed/php-sdk/reserved-keywords/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Package\PackageClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/EnumTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/reserved-keywords/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/response-property/src/Core/ArrayType.php b/seed/php-sdk/response-property/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/response-property/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/response-property/src/Core/BaseApiRequest.php b/seed/php-sdk/response-property/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/response-property/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/response-property/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/response-property/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/response-property/src/Core/Client/HttpMethod.php b/seed/php-sdk/response-property/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/response-property/src/Core/Constant.php b/seed/php-sdk/response-property/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/response-property/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/response-property/src/Core/Json/JsonDecoder.php b/seed/php-sdk/response-property/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/response-property/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/response-property/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/response-property/src/Core/Json/JsonEncoder.php b/seed/php-sdk/response-property/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/response-property/src/Core/Json/SerializableType.php b/seed/php-sdk/response-property/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/response-property/src/Core/Json/Utils.php b/seed/php-sdk/response-property/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/response-property/src/Core/JsonApiRequest.php b/seed/php-sdk/response-property/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/response-property/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/response-property/src/Core/JsonDecoder.php b/seed/php-sdk/response-property/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/response-property/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/response-property/src/Core/JsonDeserializer.php b/seed/php-sdk/response-property/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/response-property/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/response-property/src/Core/JsonEncoder.php b/seed/php-sdk/response-property/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/response-property/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/response-property/src/Core/RawClient.php b/seed/php-sdk/response-property/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/response-property/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/response-property/src/Core/SerializableType.php b/seed/php-sdk/response-property/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/response-property/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/response-property/src/Core/Types/ArrayType.php b/seed/php-sdk/response-property/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/response-property/src/Core/Types/Constant.php b/seed/php-sdk/response-property/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/response-property/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/response-property/src/Core/Union.php b/seed/php-sdk/response-property/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/response-property/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/response-property/src/Core/Utils.php b/seed/php-sdk/response-property/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/response-property/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/response-property/src/SeedClient.php b/seed/php-sdk/response-property/src/SeedClient.php index 5f052555c44..4ca47b660f7 100644 --- a/seed/php-sdk/response-property/src/SeedClient.php +++ b/seed/php-sdk/response-property/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/response-property/src/Service/ServiceClient.php b/seed/php-sdk/response-property/src/Service/ServiceClient.php index de1530cc7f8..36510e83915 100644 --- a/seed/php-sdk/response-property/src/Service/ServiceClient.php +++ b/seed/php-sdk/response-property/src/Service/ServiceClient.php @@ -2,12 +2,12 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Service\Types\Response; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Types\StringResponse; diff --git a/seed/php-sdk/response-property/src/Service/Types/Movie.php b/seed/php-sdk/response-property/src/Service/Types/Movie.php index bc45a0beb27..210580c2fc3 100644 --- a/seed/php-sdk/response-property/src/Service/Types/Movie.php +++ b/seed/php-sdk/response-property/src/Service/Types/Movie.php @@ -2,8 +2,8 @@ namespace Seed\Service\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Movie extends SerializableType { diff --git a/seed/php-sdk/response-property/src/Service/Types/Response.php b/seed/php-sdk/response-property/src/Service/Types/Response.php index 1cb268f5c84..31d797461f5 100644 --- a/seed/php-sdk/response-property/src/Service/Types/Response.php +++ b/seed/php-sdk/response-property/src/Service/Types/Response.php @@ -2,8 +2,8 @@ namespace Seed\Service\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Response extends SerializableType { diff --git a/seed/php-sdk/response-property/src/Service/Types/WithDocs.php b/seed/php-sdk/response-property/src/Service/Types/WithDocs.php index 89f885e5eea..ecb7db5d749 100644 --- a/seed/php-sdk/response-property/src/Service/Types/WithDocs.php +++ b/seed/php-sdk/response-property/src/Service/Types/WithDocs.php @@ -2,8 +2,8 @@ namespace Seed\Service\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WithDocs extends SerializableType { diff --git a/seed/php-sdk/response-property/src/Types/StringResponse.php b/seed/php-sdk/response-property/src/Types/StringResponse.php index e1b237c2ad6..0687e0bc0a2 100644 --- a/seed/php-sdk/response-property/src/Types/StringResponse.php +++ b/seed/php-sdk/response-property/src/Types/StringResponse.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StringResponse extends SerializableType { diff --git a/seed/php-sdk/response-property/src/Types/WithMetadata.php b/seed/php-sdk/response-property/src/Types/WithMetadata.php index 40ed2f2d579..3d11382982d 100644 --- a/seed/php-sdk/response-property/src/Types/WithMetadata.php +++ b/seed/php-sdk/response-property/src/Types/WithMetadata.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WithMetadata extends SerializableType { diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/response-property/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/EnumTest.php b/seed/php-sdk/response-property/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/response-property/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/response-property/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/response-property/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/response-property/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/response-property/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/response-property/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/response-property/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Completions/CompletionsClient.php b/seed/php-sdk/server-sent-event-examples/src/Completions/CompletionsClient.php index 2b145a52d09..7800a308700 100644 --- a/seed/php-sdk/server-sent-event-examples/src/Completions/CompletionsClient.php +++ b/seed/php-sdk/server-sent-event-examples/src/Completions/CompletionsClient.php @@ -2,12 +2,12 @@ namespace Seed\Completions; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Completions\Requests\StreamCompletionRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class CompletionsClient diff --git a/seed/php-sdk/server-sent-event-examples/src/Completions/Requests/StreamCompletionRequest.php b/seed/php-sdk/server-sent-event-examples/src/Completions/Requests/StreamCompletionRequest.php index 2023705caff..23a43720102 100644 --- a/seed/php-sdk/server-sent-event-examples/src/Completions/Requests/StreamCompletionRequest.php +++ b/seed/php-sdk/server-sent-event-examples/src/Completions/Requests/StreamCompletionRequest.php @@ -2,8 +2,8 @@ namespace Seed\Completions\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamCompletionRequest extends SerializableType { diff --git a/seed/php-sdk/server-sent-event-examples/src/Completions/Types/StreamedCompletion.php b/seed/php-sdk/server-sent-event-examples/src/Completions/Types/StreamedCompletion.php index 0573b87281c..19bc92207c9 100644 --- a/seed/php-sdk/server-sent-event-examples/src/Completions/Types/StreamedCompletion.php +++ b/seed/php-sdk/server-sent-event-examples/src/Completions/Types/StreamedCompletion.php @@ -2,8 +2,8 @@ namespace Seed\Completions\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamedCompletion extends SerializableType { diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/ArrayType.php b/seed/php-sdk/server-sent-event-examples/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/BaseApiRequest.php b/seed/php-sdk/server-sent-event-examples/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/server-sent-event-examples/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Client/HttpMethod.php b/seed/php-sdk/server-sent-event-examples/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Constant.php b/seed/php-sdk/server-sent-event-examples/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonDecoder.php b/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonEncoder.php b/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Json/SerializableType.php b/seed/php-sdk/server-sent-event-examples/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Json/Utils.php b/seed/php-sdk/server-sent-event-examples/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/JsonApiRequest.php b/seed/php-sdk/server-sent-event-examples/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/JsonDecoder.php b/seed/php-sdk/server-sent-event-examples/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/JsonDeserializer.php b/seed/php-sdk/server-sent-event-examples/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/JsonEncoder.php b/seed/php-sdk/server-sent-event-examples/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/RawClient.php b/seed/php-sdk/server-sent-event-examples/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/SerializableType.php b/seed/php-sdk/server-sent-event-examples/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Types/ArrayType.php b/seed/php-sdk/server-sent-event-examples/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Types/Constant.php b/seed/php-sdk/server-sent-event-examples/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Union.php b/seed/php-sdk/server-sent-event-examples/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/Core/Utils.php b/seed/php-sdk/server-sent-event-examples/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/server-sent-event-examples/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/server-sent-event-examples/src/SeedClient.php b/seed/php-sdk/server-sent-event-examples/src/SeedClient.php index d49132783ed..9c93696592b 100644 --- a/seed/php-sdk/server-sent-event-examples/src/SeedClient.php +++ b/seed/php-sdk/server-sent-event-examples/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Completions\CompletionsClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EnumTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/server-sent-event-examples/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/server-sent-events/src/Completions/CompletionsClient.php b/seed/php-sdk/server-sent-events/src/Completions/CompletionsClient.php index 2b145a52d09..7800a308700 100644 --- a/seed/php-sdk/server-sent-events/src/Completions/CompletionsClient.php +++ b/seed/php-sdk/server-sent-events/src/Completions/CompletionsClient.php @@ -2,12 +2,12 @@ namespace Seed\Completions; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Completions\Requests\StreamCompletionRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class CompletionsClient diff --git a/seed/php-sdk/server-sent-events/src/Completions/Requests/StreamCompletionRequest.php b/seed/php-sdk/server-sent-events/src/Completions/Requests/StreamCompletionRequest.php index 2023705caff..23a43720102 100644 --- a/seed/php-sdk/server-sent-events/src/Completions/Requests/StreamCompletionRequest.php +++ b/seed/php-sdk/server-sent-events/src/Completions/Requests/StreamCompletionRequest.php @@ -2,8 +2,8 @@ namespace Seed\Completions\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamCompletionRequest extends SerializableType { diff --git a/seed/php-sdk/server-sent-events/src/Completions/Types/StreamedCompletion.php b/seed/php-sdk/server-sent-events/src/Completions/Types/StreamedCompletion.php index 0573b87281c..19bc92207c9 100644 --- a/seed/php-sdk/server-sent-events/src/Completions/Types/StreamedCompletion.php +++ b/seed/php-sdk/server-sent-events/src/Completions/Types/StreamedCompletion.php @@ -2,8 +2,8 @@ namespace Seed\Completions\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamedCompletion extends SerializableType { diff --git a/seed/php-sdk/server-sent-events/src/Core/ArrayType.php b/seed/php-sdk/server-sent-events/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/BaseApiRequest.php b/seed/php-sdk/server-sent-events/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/server-sent-events/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Client/HttpMethod.php b/seed/php-sdk/server-sent-events/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Constant.php b/seed/php-sdk/server-sent-events/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Json/JsonDecoder.php b/seed/php-sdk/server-sent-events/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/server-sent-events/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Json/JsonEncoder.php b/seed/php-sdk/server-sent-events/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Json/SerializableType.php b/seed/php-sdk/server-sent-events/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Json/Utils.php b/seed/php-sdk/server-sent-events/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/JsonApiRequest.php b/seed/php-sdk/server-sent-events/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/JsonDecoder.php b/seed/php-sdk/server-sent-events/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/JsonDeserializer.php b/seed/php-sdk/server-sent-events/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/JsonEncoder.php b/seed/php-sdk/server-sent-events/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/RawClient.php b/seed/php-sdk/server-sent-events/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/SerializableType.php b/seed/php-sdk/server-sent-events/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/Types/ArrayType.php b/seed/php-sdk/server-sent-events/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Types/Constant.php b/seed/php-sdk/server-sent-events/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/server-sent-events/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/server-sent-events/src/Core/Union.php b/seed/php-sdk/server-sent-events/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/server-sent-events/src/Core/Utils.php b/seed/php-sdk/server-sent-events/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/server-sent-events/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/server-sent-events/src/SeedClient.php b/seed/php-sdk/server-sent-events/src/SeedClient.php index d49132783ed..9c93696592b 100644 --- a/seed/php-sdk/server-sent-events/src/SeedClient.php +++ b/seed/php-sdk/server-sent-events/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Completions\CompletionsClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/EnumTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/server-sent-events/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/server-sent-events/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/ArrayType.php b/seed/php-sdk/simple-fhir/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/BaseApiRequest.php b/seed/php-sdk/simple-fhir/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/simple-fhir/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Client/HttpMethod.php b/seed/php-sdk/simple-fhir/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Constant.php b/seed/php-sdk/simple-fhir/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Json/JsonDecoder.php b/seed/php-sdk/simple-fhir/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/simple-fhir/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Json/JsonEncoder.php b/seed/php-sdk/simple-fhir/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Json/SerializableType.php b/seed/php-sdk/simple-fhir/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Json/Utils.php b/seed/php-sdk/simple-fhir/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/JsonApiRequest.php b/seed/php-sdk/simple-fhir/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/JsonDecoder.php b/seed/php-sdk/simple-fhir/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/JsonDeserializer.php b/seed/php-sdk/simple-fhir/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/JsonEncoder.php b/seed/php-sdk/simple-fhir/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/RawClient.php b/seed/php-sdk/simple-fhir/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/SerializableType.php b/seed/php-sdk/simple-fhir/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/Types/ArrayType.php b/seed/php-sdk/simple-fhir/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Types/Constant.php b/seed/php-sdk/simple-fhir/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/simple-fhir/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/simple-fhir/src/Core/Union.php b/seed/php-sdk/simple-fhir/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/simple-fhir/src/Core/Utils.php b/seed/php-sdk/simple-fhir/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/simple-fhir/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/simple-fhir/src/SeedClient.php b/seed/php-sdk/simple-fhir/src/SeedClient.php index 628a8b9b68c..6094db7fa39 100644 --- a/seed/php-sdk/simple-fhir/src/SeedClient.php +++ b/seed/php-sdk/simple-fhir/src/SeedClient.php @@ -3,12 +3,12 @@ namespace Seed; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Types\Account; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/simple-fhir/src/Types/Account.php b/seed/php-sdk/simple-fhir/src/Types/Account.php index 6f010024f2f..9163fd31a80 100644 --- a/seed/php-sdk/simple-fhir/src/Types/Account.php +++ b/seed/php-sdk/simple-fhir/src/Types/Account.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Account extends SerializableType { diff --git a/seed/php-sdk/simple-fhir/src/Types/BaseResource.php b/seed/php-sdk/simple-fhir/src/Types/BaseResource.php index 83ac40eb598..516b3b3eb24 100644 --- a/seed/php-sdk/simple-fhir/src/Types/BaseResource.php +++ b/seed/php-sdk/simple-fhir/src/Types/BaseResource.php @@ -2,10 +2,10 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; -use Seed\Core\Union; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; +use Seed\Core\Types\Union; class BaseResource extends SerializableType { diff --git a/seed/php-sdk/simple-fhir/src/Types/Memo.php b/seed/php-sdk/simple-fhir/src/Types/Memo.php index b88e50d14da..f870cd8bdb3 100644 --- a/seed/php-sdk/simple-fhir/src/Types/Memo.php +++ b/seed/php-sdk/simple-fhir/src/Types/Memo.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Memo extends SerializableType { diff --git a/seed/php-sdk/simple-fhir/src/Types/Patient.php b/seed/php-sdk/simple-fhir/src/Types/Patient.php index ba5b5fead8f..5d032c76351 100644 --- a/seed/php-sdk/simple-fhir/src/Types/Patient.php +++ b/seed/php-sdk/simple-fhir/src/Types/Patient.php @@ -2,9 +2,9 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Patient extends SerializableType { diff --git a/seed/php-sdk/simple-fhir/src/Types/Practitioner.php b/seed/php-sdk/simple-fhir/src/Types/Practitioner.php index 951c9fcbc0d..6ac780ddfaf 100644 --- a/seed/php-sdk/simple-fhir/src/Types/Practitioner.php +++ b/seed/php-sdk/simple-fhir/src/Types/Practitioner.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Practitioner extends SerializableType { diff --git a/seed/php-sdk/simple-fhir/src/Types/Script.php b/seed/php-sdk/simple-fhir/src/Types/Script.php index c6694866cda..28b3c5df2b3 100644 --- a/seed/php-sdk/simple-fhir/src/Types/Script.php +++ b/seed/php-sdk/simple-fhir/src/Types/Script.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Script extends SerializableType { diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/EnumTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/simple-fhir/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/simple-fhir/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/ArrayType.php b/seed/php-sdk/single-url-environment-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/BaseApiRequest.php b/seed/php-sdk/single-url-environment-default/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/single-url-environment-default/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Client/HttpMethod.php b/seed/php-sdk/single-url-environment-default/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Constant.php b/seed/php-sdk/single-url-environment-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonDecoder.php b/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonEncoder.php b/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Json/SerializableType.php b/seed/php-sdk/single-url-environment-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Json/Utils.php b/seed/php-sdk/single-url-environment-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/JsonApiRequest.php b/seed/php-sdk/single-url-environment-default/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/JsonDecoder.php b/seed/php-sdk/single-url-environment-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/JsonDeserializer.php b/seed/php-sdk/single-url-environment-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/JsonEncoder.php b/seed/php-sdk/single-url-environment-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/RawClient.php b/seed/php-sdk/single-url-environment-default/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/SerializableType.php b/seed/php-sdk/single-url-environment-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Types/ArrayType.php b/seed/php-sdk/single-url-environment-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Types/Constant.php b/seed/php-sdk/single-url-environment-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Union.php b/seed/php-sdk/single-url-environment-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Core/Utils.php b/seed/php-sdk/single-url-environment-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/single-url-environment-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/single-url-environment-default/src/Dummy/DummyClient.php b/seed/php-sdk/single-url-environment-default/src/Dummy/DummyClient.php index 15dcecdfab5..28bbfae3d69 100644 --- a/seed/php-sdk/single-url-environment-default/src/Dummy/DummyClient.php +++ b/seed/php-sdk/single-url-environment-default/src/Dummy/DummyClient.php @@ -2,13 +2,13 @@ namespace Seed\Dummy; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/single-url-environment-default/src/SeedClient.php b/seed/php-sdk/single-url-environment-default/src/SeedClient.php index 49b43015ebb..af08670afc6 100644 --- a/seed/php-sdk/single-url-environment-default/src/SeedClient.php +++ b/seed/php-sdk/single-url-environment-default/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Dummy\DummyClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/EnumTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/single-url-environment-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/ArrayType.php b/seed/php-sdk/single-url-environment-no-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/BaseApiRequest.php b/seed/php-sdk/single-url-environment-no-default/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Client/HttpMethod.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Constant.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonDecoder.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonEncoder.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Json/SerializableType.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Json/Utils.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonApiRequest.php b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDecoder.php b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDeserializer.php b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonEncoder.php b/seed/php-sdk/single-url-environment-no-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/RawClient.php b/seed/php-sdk/single-url-environment-no-default/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/SerializableType.php b/seed/php-sdk/single-url-environment-no-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Types/ArrayType.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Types/Constant.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Union.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Core/Utils.php b/seed/php-sdk/single-url-environment-no-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/src/Dummy/DummyClient.php b/seed/php-sdk/single-url-environment-no-default/src/Dummy/DummyClient.php index ed50edb09f8..28628ee2c15 100644 --- a/seed/php-sdk/single-url-environment-no-default/src/Dummy/DummyClient.php +++ b/seed/php-sdk/single-url-environment-no-default/src/Dummy/DummyClient.php @@ -2,12 +2,12 @@ namespace Seed\Dummy; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/single-url-environment-no-default/src/SeedClient.php b/seed/php-sdk/single-url-environment-no-default/src/SeedClient.php index 49b43015ebb..af08670afc6 100644 --- a/seed/php-sdk/single-url-environment-no-default/src/SeedClient.php +++ b/seed/php-sdk/single-url-environment-no-default/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Dummy\DummyClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/EnumTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/ArrayType.php b/seed/php-sdk/streaming-parameter/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/BaseApiRequest.php b/seed/php-sdk/streaming-parameter/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/streaming-parameter/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Client/HttpMethod.php b/seed/php-sdk/streaming-parameter/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Constant.php b/seed/php-sdk/streaming-parameter/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Json/JsonDecoder.php b/seed/php-sdk/streaming-parameter/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/streaming-parameter/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Json/JsonEncoder.php b/seed/php-sdk/streaming-parameter/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Json/SerializableType.php b/seed/php-sdk/streaming-parameter/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Json/Utils.php b/seed/php-sdk/streaming-parameter/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/JsonApiRequest.php b/seed/php-sdk/streaming-parameter/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/JsonDecoder.php b/seed/php-sdk/streaming-parameter/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/JsonDeserializer.php b/seed/php-sdk/streaming-parameter/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/JsonEncoder.php b/seed/php-sdk/streaming-parameter/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/RawClient.php b/seed/php-sdk/streaming-parameter/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/SerializableType.php b/seed/php-sdk/streaming-parameter/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Types/ArrayType.php b/seed/php-sdk/streaming-parameter/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Types/Constant.php b/seed/php-sdk/streaming-parameter/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/streaming-parameter/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Union.php b/seed/php-sdk/streaming-parameter/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Core/Utils.php b/seed/php-sdk/streaming-parameter/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/streaming-parameter/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/streaming-parameter/src/Dummy/DummyClient.php b/seed/php-sdk/streaming-parameter/src/Dummy/DummyClient.php index 57f53409b6d..37646de9817 100644 --- a/seed/php-sdk/streaming-parameter/src/Dummy/DummyClient.php +++ b/seed/php-sdk/streaming-parameter/src/Dummy/DummyClient.php @@ -2,12 +2,12 @@ namespace Seed\Dummy; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Dummy\Requests\GenerateRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class DummyClient diff --git a/seed/php-sdk/streaming-parameter/src/Dummy/Requests/GenerateRequest.php b/seed/php-sdk/streaming-parameter/src/Dummy/Requests/GenerateRequest.php index cbcb220c2c8..880107cccbc 100644 --- a/seed/php-sdk/streaming-parameter/src/Dummy/Requests/GenerateRequest.php +++ b/seed/php-sdk/streaming-parameter/src/Dummy/Requests/GenerateRequest.php @@ -2,8 +2,8 @@ namespace Seed\Dummy\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GenerateRequest extends SerializableType { diff --git a/seed/php-sdk/streaming-parameter/src/Dummy/Types/RegularResponse.php b/seed/php-sdk/streaming-parameter/src/Dummy/Types/RegularResponse.php index 8d78c1cd369..c9ab266d146 100644 --- a/seed/php-sdk/streaming-parameter/src/Dummy/Types/RegularResponse.php +++ b/seed/php-sdk/streaming-parameter/src/Dummy/Types/RegularResponse.php @@ -2,8 +2,8 @@ namespace Seed\Dummy\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RegularResponse extends SerializableType { diff --git a/seed/php-sdk/streaming-parameter/src/Dummy/Types/StreamResponse.php b/seed/php-sdk/streaming-parameter/src/Dummy/Types/StreamResponse.php index 548fcea5181..f65bdc35518 100644 --- a/seed/php-sdk/streaming-parameter/src/Dummy/Types/StreamResponse.php +++ b/seed/php-sdk/streaming-parameter/src/Dummy/Types/StreamResponse.php @@ -2,8 +2,8 @@ namespace Seed\Dummy\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamResponse extends SerializableType { diff --git a/seed/php-sdk/streaming-parameter/src/SeedClient.php b/seed/php-sdk/streaming-parameter/src/SeedClient.php index 4551b127021..02964b6e273 100644 --- a/seed/php-sdk/streaming-parameter/src/SeedClient.php +++ b/seed/php-sdk/streaming-parameter/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Dummy\DummyClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/EnumTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/streaming-parameter/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/streaming/src/Core/ArrayType.php b/seed/php-sdk/streaming/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/streaming/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/streaming/src/Core/BaseApiRequest.php b/seed/php-sdk/streaming/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/streaming/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/streaming/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/streaming/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/streaming/src/Core/Client/HttpMethod.php b/seed/php-sdk/streaming/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/streaming/src/Core/Constant.php b/seed/php-sdk/streaming/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/streaming/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/streaming/src/Core/Json/JsonDecoder.php b/seed/php-sdk/streaming/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/streaming/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/streaming/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/streaming/src/Core/Json/JsonEncoder.php b/seed/php-sdk/streaming/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/streaming/src/Core/Json/SerializableType.php b/seed/php-sdk/streaming/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/streaming/src/Core/Json/Utils.php b/seed/php-sdk/streaming/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/streaming/src/Core/JsonApiRequest.php b/seed/php-sdk/streaming/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/streaming/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/streaming/src/Core/JsonDecoder.php b/seed/php-sdk/streaming/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/streaming/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/streaming/src/Core/JsonDeserializer.php b/seed/php-sdk/streaming/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/streaming/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/streaming/src/Core/JsonEncoder.php b/seed/php-sdk/streaming/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/streaming/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/streaming/src/Core/RawClient.php b/seed/php-sdk/streaming/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/streaming/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/streaming/src/Core/SerializableType.php b/seed/php-sdk/streaming/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/streaming/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/streaming/src/Core/Types/ArrayType.php b/seed/php-sdk/streaming/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/streaming/src/Core/Types/Constant.php b/seed/php-sdk/streaming/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/streaming/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/streaming/src/Core/Union.php b/seed/php-sdk/streaming/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/streaming/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/streaming/src/Core/Utils.php b/seed/php-sdk/streaming/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/streaming/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/streaming/src/Dummy/DummyClient.php b/seed/php-sdk/streaming/src/Dummy/DummyClient.php index 7fa2a434d55..9c3d1c50b1d 100644 --- a/seed/php-sdk/streaming/src/Dummy/DummyClient.php +++ b/seed/php-sdk/streaming/src/Dummy/DummyClient.php @@ -2,12 +2,12 @@ namespace Seed\Dummy; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Dummy\Requests\GenerateStreamRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; use Seed\Dummy\Requests\Generateequest; use Seed\Dummy\Types\StreamResponse; diff --git a/seed/php-sdk/streaming/src/Dummy/Requests/GenerateStreamRequest.php b/seed/php-sdk/streaming/src/Dummy/Requests/GenerateStreamRequest.php index 057733df41e..8a8f31448c1 100644 --- a/seed/php-sdk/streaming/src/Dummy/Requests/GenerateStreamRequest.php +++ b/seed/php-sdk/streaming/src/Dummy/Requests/GenerateStreamRequest.php @@ -2,8 +2,8 @@ namespace Seed\Dummy\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GenerateStreamRequest extends SerializableType { diff --git a/seed/php-sdk/streaming/src/Dummy/Requests/Generateequest.php b/seed/php-sdk/streaming/src/Dummy/Requests/Generateequest.php index a63d0d6a70e..c4a4f8b3c54 100644 --- a/seed/php-sdk/streaming/src/Dummy/Requests/Generateequest.php +++ b/seed/php-sdk/streaming/src/Dummy/Requests/Generateequest.php @@ -2,8 +2,8 @@ namespace Seed\Dummy\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Generateequest extends SerializableType { diff --git a/seed/php-sdk/streaming/src/Dummy/Types/StreamResponse.php b/seed/php-sdk/streaming/src/Dummy/Types/StreamResponse.php index 548fcea5181..f65bdc35518 100644 --- a/seed/php-sdk/streaming/src/Dummy/Types/StreamResponse.php +++ b/seed/php-sdk/streaming/src/Dummy/Types/StreamResponse.php @@ -2,8 +2,8 @@ namespace Seed\Dummy\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StreamResponse extends SerializableType { diff --git a/seed/php-sdk/streaming/src/SeedClient.php b/seed/php-sdk/streaming/src/SeedClient.php index 4551b127021..02964b6e273 100644 --- a/seed/php-sdk/streaming/src/SeedClient.php +++ b/seed/php-sdk/streaming/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Dummy\DummyClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/streaming/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/EnumTest.php b/seed/php-sdk/streaming/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/streaming/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/streaming/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/streaming/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/streaming/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/streaming/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/streaming/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/streaming/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/trace/src/Admin/AdminClient.php b/seed/php-sdk/trace/src/Admin/AdminClient.php index c8c2059a2f1..fef7eb7c8fe 100644 --- a/seed/php-sdk/trace/src/Admin/AdminClient.php +++ b/seed/php-sdk/trace/src/Admin/AdminClient.php @@ -2,18 +2,18 @@ namespace Seed\Admin; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; use Seed\Submission\Types\TestSubmissionUpdate; use Seed\Submission\Types\WorkspaceSubmissionUpdate; use Seed\Admin\Requests\StoreTracedTestCaseRequest; use Seed\Submission\Types\TraceResponseV2; -use Seed\Core\JsonSerializer; +use Seed\Core\Json\JsonSerializer; use Seed\Admin\Requests\StoreTracedWorkspaceRequest; class AdminClient diff --git a/seed/php-sdk/trace/src/Admin/Requests/StoreTracedTestCaseRequest.php b/seed/php-sdk/trace/src/Admin/Requests/StoreTracedTestCaseRequest.php index 97e7c124f44..fd306cc1746 100644 --- a/seed/php-sdk/trace/src/Admin/Requests/StoreTracedTestCaseRequest.php +++ b/seed/php-sdk/trace/src/Admin/Requests/StoreTracedTestCaseRequest.php @@ -2,11 +2,11 @@ namespace Seed\Admin\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Submission\Types\TestCaseResultWithStdout; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; use Seed\Submission\Types\TraceResponse; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class StoreTracedTestCaseRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Admin/Requests/StoreTracedWorkspaceRequest.php b/seed/php-sdk/trace/src/Admin/Requests/StoreTracedWorkspaceRequest.php index e564ce6f06c..906200b8b0a 100644 --- a/seed/php-sdk/trace/src/Admin/Requests/StoreTracedWorkspaceRequest.php +++ b/seed/php-sdk/trace/src/Admin/Requests/StoreTracedWorkspaceRequest.php @@ -2,11 +2,11 @@ namespace Seed\Admin\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Submission\Types\WorkspaceRunDetails; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; use Seed\Submission\Types\TraceResponse; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class StoreTracedWorkspaceRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/BinaryTreeNodeAndTreeValue.php b/seed/php-sdk/trace/src/Commons/Types/BinaryTreeNodeAndTreeValue.php index 7b1b7733cf7..a0d516085f0 100644 --- a/seed/php-sdk/trace/src/Commons/Types/BinaryTreeNodeAndTreeValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/BinaryTreeNodeAndTreeValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BinaryTreeNodeAndTreeValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/BinaryTreeNodeValue.php b/seed/php-sdk/trace/src/Commons/Types/BinaryTreeNodeValue.php index 6480c5ec944..645a1f3959d 100644 --- a/seed/php-sdk/trace/src/Commons/Types/BinaryTreeNodeValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/BinaryTreeNodeValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BinaryTreeNodeValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/BinaryTreeValue.php b/seed/php-sdk/trace/src/Commons/Types/BinaryTreeValue.php index 00ea4f76572..4971b11aacc 100644 --- a/seed/php-sdk/trace/src/Commons/Types/BinaryTreeValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/BinaryTreeValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class BinaryTreeValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/DebugKeyValuePairs.php b/seed/php-sdk/trace/src/Commons/Types/DebugKeyValuePairs.php index 2f81bafdeb4..b23102409d3 100644 --- a/seed/php-sdk/trace/src/Commons/Types/DebugKeyValuePairs.php +++ b/seed/php-sdk/trace/src/Commons/Types/DebugKeyValuePairs.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DebugKeyValuePairs extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/DebugMapValue.php b/seed/php-sdk/trace/src/Commons/Types/DebugMapValue.php index 7fa8fcc0215..f45fe8f9415 100644 --- a/seed/php-sdk/trace/src/Commons/Types/DebugMapValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/DebugMapValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DebugMapValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListNodeAndListValue.php b/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListNodeAndListValue.php index a50127e4844..52fd953dc54 100644 --- a/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListNodeAndListValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListNodeAndListValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DoublyLinkedListNodeAndListValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListNodeValue.php b/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListNodeValue.php index 82f8ed22fe5..8502e31272f 100644 --- a/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListNodeValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListNodeValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DoublyLinkedListNodeValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListValue.php b/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListValue.php index cb87e897cf7..24e0c7ea6c9 100644 --- a/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/DoublyLinkedListValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DoublyLinkedListValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/FileInfo.php b/seed/php-sdk/trace/src/Commons/Types/FileInfo.php index 4f1be01416d..028901ab0e4 100644 --- a/seed/php-sdk/trace/src/Commons/Types/FileInfo.php +++ b/seed/php-sdk/trace/src/Commons/Types/FileInfo.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FileInfo extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/GenericValue.php b/seed/php-sdk/trace/src/Commons/Types/GenericValue.php index 5b6f2753199..d127bdb4c24 100644 --- a/seed/php-sdk/trace/src/Commons/Types/GenericValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/GenericValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GenericValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/KeyValuePair.php b/seed/php-sdk/trace/src/Commons/Types/KeyValuePair.php index ad073640d29..04db1a60ded 100644 --- a/seed/php-sdk/trace/src/Commons/Types/KeyValuePair.php +++ b/seed/php-sdk/trace/src/Commons/Types/KeyValuePair.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class KeyValuePair extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/ListType.php b/seed/php-sdk/trace/src/Commons/Types/ListType.php index 78bd43d127b..bfa235d7406 100644 --- a/seed/php-sdk/trace/src/Commons/Types/ListType.php +++ b/seed/php-sdk/trace/src/Commons/Types/ListType.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ListType extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/MapType.php b/seed/php-sdk/trace/src/Commons/Types/MapType.php index 26a02882e32..cf3a3cea9b8 100644 --- a/seed/php-sdk/trace/src/Commons/Types/MapType.php +++ b/seed/php-sdk/trace/src/Commons/Types/MapType.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class MapType extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/MapValue.php b/seed/php-sdk/trace/src/Commons/Types/MapValue.php index 99169b6ab43..80902301def 100644 --- a/seed/php-sdk/trace/src/Commons/Types/MapValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/MapValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class MapValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListNodeAndListValue.php b/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListNodeAndListValue.php index e307d712872..c996f73bf16 100644 --- a/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListNodeAndListValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListNodeAndListValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SinglyLinkedListNodeAndListValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListNodeValue.php b/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListNodeValue.php index 884d6f85bd0..24c8ae2a075 100644 --- a/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListNodeValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListNodeValue.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SinglyLinkedListNodeValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListValue.php b/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListValue.php index 1e4aa55c2e7..e5b1d39570f 100644 --- a/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListValue.php +++ b/seed/php-sdk/trace/src/Commons/Types/SinglyLinkedListValue.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class SinglyLinkedListValue extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/TestCase.php b/seed/php-sdk/trace/src/Commons/Types/TestCase.php index 31208657fda..0020764b534 100644 --- a/seed/php-sdk/trace/src/Commons/Types/TestCase.php +++ b/seed/php-sdk/trace/src/Commons/Types/TestCase.php @@ -2,9 +2,9 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCase extends SerializableType { diff --git a/seed/php-sdk/trace/src/Commons/Types/TestCaseWithExpectedResult.php b/seed/php-sdk/trace/src/Commons/Types/TestCaseWithExpectedResult.php index 57c3c32c2ee..dfdcd39c8e4 100644 --- a/seed/php-sdk/trace/src/Commons/Types/TestCaseWithExpectedResult.php +++ b/seed/php-sdk/trace/src/Commons/Types/TestCaseWithExpectedResult.php @@ -2,8 +2,8 @@ namespace Seed\Commons\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseWithExpectedResult extends SerializableType { diff --git a/seed/php-sdk/trace/src/Core/ArrayType.php b/seed/php-sdk/trace/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/trace/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/trace/src/Core/BaseApiRequest.php b/seed/php-sdk/trace/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/trace/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/trace/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/trace/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/trace/src/Core/Client/HttpMethod.php b/seed/php-sdk/trace/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/trace/src/Core/Constant.php b/seed/php-sdk/trace/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/trace/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/trace/src/Core/Json/JsonDecoder.php b/seed/php-sdk/trace/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/trace/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/trace/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/trace/src/Core/Json/JsonEncoder.php b/seed/php-sdk/trace/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/trace/src/Core/Json/SerializableType.php b/seed/php-sdk/trace/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/trace/src/Core/Json/Utils.php b/seed/php-sdk/trace/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/trace/src/Core/JsonApiRequest.php b/seed/php-sdk/trace/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/trace/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/trace/src/Core/JsonDecoder.php b/seed/php-sdk/trace/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/trace/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/trace/src/Core/JsonDeserializer.php b/seed/php-sdk/trace/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/trace/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/trace/src/Core/JsonEncoder.php b/seed/php-sdk/trace/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/trace/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/trace/src/Core/RawClient.php b/seed/php-sdk/trace/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/trace/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/trace/src/Core/SerializableType.php b/seed/php-sdk/trace/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/trace/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/trace/src/Core/Types/ArrayType.php b/seed/php-sdk/trace/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/trace/src/Core/Types/Constant.php b/seed/php-sdk/trace/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/trace/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/trace/src/Core/Union.php b/seed/php-sdk/trace/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/trace/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/trace/src/Core/Utils.php b/seed/php-sdk/trace/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/trace/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/trace/src/Homepage/HomepageClient.php b/seed/php-sdk/trace/src/Homepage/HomepageClient.php index 032a0cb9e19..0fc880fe10f 100644 --- a/seed/php-sdk/trace/src/Homepage/HomepageClient.php +++ b/seed/php-sdk/trace/src/Homepage/HomepageClient.php @@ -2,16 +2,16 @@ namespace Seed\Homepage; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; -use Seed\Core\JsonSerializer; +use Seed\Core\Json\JsonSerializer; class HomepageClient { diff --git a/seed/php-sdk/trace/src/LangServer/Types/LangServerRequest.php b/seed/php-sdk/trace/src/LangServer/Types/LangServerRequest.php index 62f525747b7..8e3ad005ffa 100644 --- a/seed/php-sdk/trace/src/LangServer/Types/LangServerRequest.php +++ b/seed/php-sdk/trace/src/LangServer/Types/LangServerRequest.php @@ -2,8 +2,8 @@ namespace Seed\LangServer\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class LangServerRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/LangServer/Types/LangServerResponse.php b/seed/php-sdk/trace/src/LangServer/Types/LangServerResponse.php index 05945db48b2..685c3a659af 100644 --- a/seed/php-sdk/trace/src/LangServer/Types/LangServerResponse.php +++ b/seed/php-sdk/trace/src/LangServer/Types/LangServerResponse.php @@ -2,8 +2,8 @@ namespace Seed\LangServer\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class LangServerResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Migration/MigrationClient.php b/seed/php-sdk/trace/src/Migration/MigrationClient.php index 0605aeaf104..b547f059563 100644 --- a/seed/php-sdk/trace/src/Migration/MigrationClient.php +++ b/seed/php-sdk/trace/src/Migration/MigrationClient.php @@ -2,15 +2,15 @@ namespace Seed\Migration; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Migration\Requests\GetAttemptedMigrationsRequest; use Seed\Migration\Types\Migration; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/trace/src/Migration/Requests/GetAttemptedMigrationsRequest.php b/seed/php-sdk/trace/src/Migration/Requests/GetAttemptedMigrationsRequest.php index ce2954acabb..1130dfef140 100644 --- a/seed/php-sdk/trace/src/Migration/Requests/GetAttemptedMigrationsRequest.php +++ b/seed/php-sdk/trace/src/Migration/Requests/GetAttemptedMigrationsRequest.php @@ -2,7 +2,7 @@ namespace Seed\Migration\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetAttemptedMigrationsRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Migration/Types/Migration.php b/seed/php-sdk/trace/src/Migration/Types/Migration.php index 9657137180e..20afd00238a 100644 --- a/seed/php-sdk/trace/src/Migration/Types/Migration.php +++ b/seed/php-sdk/trace/src/Migration/Types/Migration.php @@ -2,8 +2,8 @@ namespace Seed\Migration\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Migration extends SerializableType { diff --git a/seed/php-sdk/trace/src/Playlist/PlaylistClient.php b/seed/php-sdk/trace/src/Playlist/PlaylistClient.php index 319a3918149..9bde69b472a 100644 --- a/seed/php-sdk/trace/src/Playlist/PlaylistClient.php +++ b/seed/php-sdk/trace/src/Playlist/PlaylistClient.php @@ -2,19 +2,19 @@ namespace Seed\Playlist; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Playlist\Requests\CreatePlaylistRequest; use Seed\Playlist\Types\Playlist; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\Constant; -use Seed\Core\JsonApiRequest; +use Seed\Core\Types\Constant; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Playlist\Requests\GetPlaylistsRequest; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonDecoder; use Seed\Playlist\Types\UpdatePlaylistRequest; class PlaylistClient diff --git a/seed/php-sdk/trace/src/Playlist/Requests/CreatePlaylistRequest.php b/seed/php-sdk/trace/src/Playlist/Requests/CreatePlaylistRequest.php index efdc6f571b8..13bdb1c680a 100644 --- a/seed/php-sdk/trace/src/Playlist/Requests/CreatePlaylistRequest.php +++ b/seed/php-sdk/trace/src/Playlist/Requests/CreatePlaylistRequest.php @@ -2,7 +2,7 @@ namespace Seed\Playlist\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; use Seed\Playlist\Types\PlaylistCreateRequest; diff --git a/seed/php-sdk/trace/src/Playlist/Requests/GetPlaylistsRequest.php b/seed/php-sdk/trace/src/Playlist/Requests/GetPlaylistsRequest.php index 0825f8722e8..6345b70f419 100644 --- a/seed/php-sdk/trace/src/Playlist/Requests/GetPlaylistsRequest.php +++ b/seed/php-sdk/trace/src/Playlist/Requests/GetPlaylistsRequest.php @@ -2,7 +2,7 @@ namespace Seed\Playlist\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetPlaylistsRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Playlist/Types/Playlist.php b/seed/php-sdk/trace/src/Playlist/Types/Playlist.php index 8939a84d0ed..8b16caa92f9 100644 --- a/seed/php-sdk/trace/src/Playlist/Types/Playlist.php +++ b/seed/php-sdk/trace/src/Playlist/Types/Playlist.php @@ -2,8 +2,8 @@ namespace Seed\Playlist\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Playlist extends SerializableType { diff --git a/seed/php-sdk/trace/src/Playlist/Types/PlaylistCreateRequest.php b/seed/php-sdk/trace/src/Playlist/Types/PlaylistCreateRequest.php index 8f2dc968c56..3558106510e 100644 --- a/seed/php-sdk/trace/src/Playlist/Types/PlaylistCreateRequest.php +++ b/seed/php-sdk/trace/src/Playlist/Types/PlaylistCreateRequest.php @@ -2,9 +2,9 @@ namespace Seed\Playlist\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class PlaylistCreateRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Playlist/Types/UpdatePlaylistRequest.php b/seed/php-sdk/trace/src/Playlist/Types/UpdatePlaylistRequest.php index e0069313125..461ae783772 100644 --- a/seed/php-sdk/trace/src/Playlist/Types/UpdatePlaylistRequest.php +++ b/seed/php-sdk/trace/src/Playlist/Types/UpdatePlaylistRequest.php @@ -2,9 +2,9 @@ namespace Seed\Playlist\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class UpdatePlaylistRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Problem/ProblemClient.php b/seed/php-sdk/trace/src/Problem/ProblemClient.php index 383d14466cb..69be4b8a837 100644 --- a/seed/php-sdk/trace/src/Problem/ProblemClient.php +++ b/seed/php-sdk/trace/src/Problem/ProblemClient.php @@ -2,14 +2,14 @@ namespace Seed\Problem; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Problem\Types\CreateProblemRequest; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Problem\Types\UpdateProblemResponse; diff --git a/seed/php-sdk/trace/src/Problem/Requests/GetDefaultStarterFilesRequest.php b/seed/php-sdk/trace/src/Problem/Requests/GetDefaultStarterFilesRequest.php index 4300cb07fde..ccd9b196ff7 100644 --- a/seed/php-sdk/trace/src/Problem/Requests/GetDefaultStarterFilesRequest.php +++ b/seed/php-sdk/trace/src/Problem/Requests/GetDefaultStarterFilesRequest.php @@ -2,10 +2,10 @@ namespace Seed\Problem\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Problem\Types\VariableTypeAndName; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetDefaultStarterFilesRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Problem/Types/CreateProblemRequest.php b/seed/php-sdk/trace/src/Problem/Types/CreateProblemRequest.php index 640f5ec6ca6..fbb40cbcdc5 100644 --- a/seed/php-sdk/trace/src/Problem/Types/CreateProblemRequest.php +++ b/seed/php-sdk/trace/src/Problem/Types/CreateProblemRequest.php @@ -2,10 +2,10 @@ namespace Seed\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Commons\Types\TestCaseWithExpectedResult; class CreateProblemRequest extends SerializableType diff --git a/seed/php-sdk/trace/src/Problem/Types/GenericCreateProblemError.php b/seed/php-sdk/trace/src/Problem/Types/GenericCreateProblemError.php index 63758928705..db06db93108 100644 --- a/seed/php-sdk/trace/src/Problem/Types/GenericCreateProblemError.php +++ b/seed/php-sdk/trace/src/Problem/Types/GenericCreateProblemError.php @@ -2,8 +2,8 @@ namespace Seed\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GenericCreateProblemError extends SerializableType { diff --git a/seed/php-sdk/trace/src/Problem/Types/GetDefaultStarterFilesResponse.php b/seed/php-sdk/trace/src/Problem/Types/GetDefaultStarterFilesResponse.php index bdcf0d24b2f..a7742714215 100644 --- a/seed/php-sdk/trace/src/Problem/Types/GetDefaultStarterFilesResponse.php +++ b/seed/php-sdk/trace/src/Problem/Types/GetDefaultStarterFilesResponse.php @@ -2,10 +2,10 @@ namespace Seed\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetDefaultStarterFilesResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Problem/Types/ProblemDescription.php b/seed/php-sdk/trace/src/Problem/Types/ProblemDescription.php index 6fc21b96fcf..42c1225a9fc 100644 --- a/seed/php-sdk/trace/src/Problem/Types/ProblemDescription.php +++ b/seed/php-sdk/trace/src/Problem/Types/ProblemDescription.php @@ -2,9 +2,9 @@ namespace Seed\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ProblemDescription extends SerializableType { diff --git a/seed/php-sdk/trace/src/Problem/Types/ProblemFiles.php b/seed/php-sdk/trace/src/Problem/Types/ProblemFiles.php index 4daab205eee..47ab92897e8 100644 --- a/seed/php-sdk/trace/src/Problem/Types/ProblemFiles.php +++ b/seed/php-sdk/trace/src/Problem/Types/ProblemFiles.php @@ -2,10 +2,10 @@ namespace Seed\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\FileInfo; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class ProblemFiles extends SerializableType { diff --git a/seed/php-sdk/trace/src/Problem/Types/ProblemInfo.php b/seed/php-sdk/trace/src/Problem/Types/ProblemInfo.php index d137272d31c..24908807c8f 100644 --- a/seed/php-sdk/trace/src/Problem/Types/ProblemInfo.php +++ b/seed/php-sdk/trace/src/Problem/Types/ProblemInfo.php @@ -2,10 +2,10 @@ namespace Seed\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Commons\Types\TestCaseWithExpectedResult; class ProblemInfo extends SerializableType diff --git a/seed/php-sdk/trace/src/Problem/Types/UpdateProblemResponse.php b/seed/php-sdk/trace/src/Problem/Types/UpdateProblemResponse.php index 3e61a6598f9..699d9853391 100644 --- a/seed/php-sdk/trace/src/Problem/Types/UpdateProblemResponse.php +++ b/seed/php-sdk/trace/src/Problem/Types/UpdateProblemResponse.php @@ -2,8 +2,8 @@ namespace Seed\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class UpdateProblemResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Problem/Types/VariableTypeAndName.php b/seed/php-sdk/trace/src/Problem/Types/VariableTypeAndName.php index 0438fa59b14..b467ee2b7e4 100644 --- a/seed/php-sdk/trace/src/Problem/Types/VariableTypeAndName.php +++ b/seed/php-sdk/trace/src/Problem/Types/VariableTypeAndName.php @@ -2,8 +2,8 @@ namespace Seed\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class VariableTypeAndName extends SerializableType { diff --git a/seed/php-sdk/trace/src/SeedClient.php b/seed/php-sdk/trace/src/SeedClient.php index df52efc639c..4c54629b3b8 100644 --- a/seed/php-sdk/trace/src/SeedClient.php +++ b/seed/php-sdk/trace/src/SeedClient.php @@ -11,7 +11,7 @@ use Seed\Submission\SubmissionClient; use Seed\Sysprop\SyspropClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/trace/src/Submission/SubmissionClient.php b/seed/php-sdk/trace/src/Submission/SubmissionClient.php index b13672c5395..9e4523658ef 100644 --- a/seed/php-sdk/trace/src/Submission/SubmissionClient.php +++ b/seed/php-sdk/trace/src/Submission/SubmissionClient.php @@ -2,14 +2,14 @@ namespace Seed\Submission; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Commons\Types\Language; use Seed\Submission\Types\ExecutionSessionResponse; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Submission\Types\GetExecutionSessionStateResponse; diff --git a/seed/php-sdk/trace/src/Submission/Types/BuildingExecutorResponse.php b/seed/php-sdk/trace/src/Submission/Types/BuildingExecutorResponse.php index af869dc95a5..a5bb5593cdd 100644 --- a/seed/php-sdk/trace/src/Submission/Types/BuildingExecutorResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/BuildingExecutorResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BuildingExecutorResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/CompileError.php b/seed/php-sdk/trace/src/Submission/Types/CompileError.php index 58542a9c282..fc9436e7046 100644 --- a/seed/php-sdk/trace/src/Submission/Types/CompileError.php +++ b/seed/php-sdk/trace/src/Submission/Types/CompileError.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CompileError extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/CustomTestCasesUnsupported.php b/seed/php-sdk/trace/src/Submission/Types/CustomTestCasesUnsupported.php index 1193b18efbe..aca40c6d0d1 100644 --- a/seed/php-sdk/trace/src/Submission/Types/CustomTestCasesUnsupported.php +++ b/seed/php-sdk/trace/src/Submission/Types/CustomTestCasesUnsupported.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class CustomTestCasesUnsupported extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/ErroredResponse.php b/seed/php-sdk/trace/src/Submission/Types/ErroredResponse.php index d9fb448f30c..c9e3ab5fe40 100644 --- a/seed/php-sdk/trace/src/Submission/Types/ErroredResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/ErroredResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ErroredResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/ExceptionInfo.php b/seed/php-sdk/trace/src/Submission/Types/ExceptionInfo.php index 01f8e681893..52148fba41e 100644 --- a/seed/php-sdk/trace/src/Submission/Types/ExceptionInfo.php +++ b/seed/php-sdk/trace/src/Submission/Types/ExceptionInfo.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExceptionInfo extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/ExecutionSessionResponse.php b/seed/php-sdk/trace/src/Submission/Types/ExecutionSessionResponse.php index b10cf7dce65..8efc1dbe2ab 100644 --- a/seed/php-sdk/trace/src/Submission/Types/ExecutionSessionResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/ExecutionSessionResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\Language; class ExecutionSessionResponse extends SerializableType diff --git a/seed/php-sdk/trace/src/Submission/Types/ExecutionSessionState.php b/seed/php-sdk/trace/src/Submission/Types/ExecutionSessionState.php index 7adf96b1474..0b83f81bf71 100644 --- a/seed/php-sdk/trace/src/Submission/Types/ExecutionSessionState.php +++ b/seed/php-sdk/trace/src/Submission/Types/ExecutionSessionState.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\Language; class ExecutionSessionState extends SerializableType diff --git a/seed/php-sdk/trace/src/Submission/Types/ExistingSubmissionExecuting.php b/seed/php-sdk/trace/src/Submission/Types/ExistingSubmissionExecuting.php index 5abbd930ca6..3f0240a13b7 100644 --- a/seed/php-sdk/trace/src/Submission/Types/ExistingSubmissionExecuting.php +++ b/seed/php-sdk/trace/src/Submission/Types/ExistingSubmissionExecuting.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExistingSubmissionExecuting extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/ExpressionLocation.php b/seed/php-sdk/trace/src/Submission/Types/ExpressionLocation.php index 0cd4c50590c..41c07f0c661 100644 --- a/seed/php-sdk/trace/src/Submission/Types/ExpressionLocation.php +++ b/seed/php-sdk/trace/src/Submission/Types/ExpressionLocation.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class ExpressionLocation extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/FinishedResponse.php b/seed/php-sdk/trace/src/Submission/Types/FinishedResponse.php index aeec4b23185..ef878c2d54e 100644 --- a/seed/php-sdk/trace/src/Submission/Types/FinishedResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/FinishedResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FinishedResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/GetExecutionSessionStateResponse.php b/seed/php-sdk/trace/src/Submission/Types/GetExecutionSessionStateResponse.php index a470c108619..d49db454a3d 100644 --- a/seed/php-sdk/trace/src/Submission/Types/GetExecutionSessionStateResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/GetExecutionSessionStateResponse.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetExecutionSessionStateResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/GetSubmissionStateResponse.php b/seed/php-sdk/trace/src/Submission/Types/GetSubmissionStateResponse.php index 80dc8a79e8a..dbf50f6339a 100644 --- a/seed/php-sdk/trace/src/Submission/Types/GetSubmissionStateResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/GetSubmissionStateResponse.php @@ -2,10 +2,10 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; -use Seed\Core\JsonProperty; -use Seed\Core\DateType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\DateType; use Seed\Commons\Types\Language; class GetSubmissionStateResponse extends SerializableType diff --git a/seed/php-sdk/trace/src/Submission/Types/GetTraceResponsesPageRequest.php b/seed/php-sdk/trace/src/Submission/Types/GetTraceResponsesPageRequest.php index 9eff9ff8846..a20be20a3f1 100644 --- a/seed/php-sdk/trace/src/Submission/Types/GetTraceResponsesPageRequest.php +++ b/seed/php-sdk/trace/src/Submission/Types/GetTraceResponsesPageRequest.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetTraceResponsesPageRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/GradedResponse.php b/seed/php-sdk/trace/src/Submission/Types/GradedResponse.php index 63de2cb4301..c23b78feb19 100644 --- a/seed/php-sdk/trace/src/Submission/Types/GradedResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/GradedResponse.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GradedResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/GradedResponseV2.php b/seed/php-sdk/trace/src/Submission/Types/GradedResponseV2.php index 82ca2d28d0b..6d71b54dbb7 100644 --- a/seed/php-sdk/trace/src/Submission/Types/GradedResponseV2.php +++ b/seed/php-sdk/trace/src/Submission/Types/GradedResponseV2.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GradedResponseV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/GradedTestCaseUpdate.php b/seed/php-sdk/trace/src/Submission/Types/GradedTestCaseUpdate.php index 6b5d73054e3..639ff87b15b 100644 --- a/seed/php-sdk/trace/src/Submission/Types/GradedTestCaseUpdate.php +++ b/seed/php-sdk/trace/src/Submission/Types/GradedTestCaseUpdate.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GradedTestCaseUpdate extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/InitializeProblemRequest.php b/seed/php-sdk/trace/src/Submission/Types/InitializeProblemRequest.php index dc25cfcee19..bd92b069cdb 100644 --- a/seed/php-sdk/trace/src/Submission/Types/InitializeProblemRequest.php +++ b/seed/php-sdk/trace/src/Submission/Types/InitializeProblemRequest.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class InitializeProblemRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/InternalError.php b/seed/php-sdk/trace/src/Submission/Types/InternalError.php index 0efa8cbaf01..d65af0fd316 100644 --- a/seed/php-sdk/trace/src/Submission/Types/InternalError.php +++ b/seed/php-sdk/trace/src/Submission/Types/InternalError.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class InternalError extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/InvalidRequestResponse.php b/seed/php-sdk/trace/src/Submission/Types/InvalidRequestResponse.php index 345770ba082..394b984dd60 100644 --- a/seed/php-sdk/trace/src/Submission/Types/InvalidRequestResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/InvalidRequestResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class InvalidRequestResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/LightweightStackframeInformation.php b/seed/php-sdk/trace/src/Submission/Types/LightweightStackframeInformation.php index 785e2ece89e..ea291cc4737 100644 --- a/seed/php-sdk/trace/src/Submission/Types/LightweightStackframeInformation.php +++ b/seed/php-sdk/trace/src/Submission/Types/LightweightStackframeInformation.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class LightweightStackframeInformation extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/RecordedResponseNotification.php b/seed/php-sdk/trace/src/Submission/Types/RecordedResponseNotification.php index 60309760bb2..2a44b3ea583 100644 --- a/seed/php-sdk/trace/src/Submission/Types/RecordedResponseNotification.php +++ b/seed/php-sdk/trace/src/Submission/Types/RecordedResponseNotification.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RecordedResponseNotification extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/RecordedTestCaseUpdate.php b/seed/php-sdk/trace/src/Submission/Types/RecordedTestCaseUpdate.php index 1c92ffce33e..17113f35226 100644 --- a/seed/php-sdk/trace/src/Submission/Types/RecordedTestCaseUpdate.php +++ b/seed/php-sdk/trace/src/Submission/Types/RecordedTestCaseUpdate.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RecordedTestCaseUpdate extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/RecordingResponseNotification.php b/seed/php-sdk/trace/src/Submission/Types/RecordingResponseNotification.php index 08ee80081b5..2737d930f6b 100644 --- a/seed/php-sdk/trace/src/Submission/Types/RecordingResponseNotification.php +++ b/seed/php-sdk/trace/src/Submission/Types/RecordingResponseNotification.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RecordingResponseNotification extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/RunningResponse.php b/seed/php-sdk/trace/src/Submission/Types/RunningResponse.php index e40c4c4f56c..bab6c5780a2 100644 --- a/seed/php-sdk/trace/src/Submission/Types/RunningResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/RunningResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RunningResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/RuntimeError.php b/seed/php-sdk/trace/src/Submission/Types/RuntimeError.php index cf23319a0f3..9e9f47d79d8 100644 --- a/seed/php-sdk/trace/src/Submission/Types/RuntimeError.php +++ b/seed/php-sdk/trace/src/Submission/Types/RuntimeError.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class RuntimeError extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/Scope.php b/seed/php-sdk/trace/src/Submission/Types/Scope.php index 369c4fd9970..a975fc1cab8 100644 --- a/seed/php-sdk/trace/src/Submission/Types/Scope.php +++ b/seed/php-sdk/trace/src/Submission/Types/Scope.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Scope extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/StackFrame.php b/seed/php-sdk/trace/src/Submission/Types/StackFrame.php index bb19c0a288a..ae90aaea247 100644 --- a/seed/php-sdk/trace/src/Submission/Types/StackFrame.php +++ b/seed/php-sdk/trace/src/Submission/Types/StackFrame.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class StackFrame extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/StackInformation.php b/seed/php-sdk/trace/src/Submission/Types/StackInformation.php index 202a5d8c3b9..35771f12a7e 100644 --- a/seed/php-sdk/trace/src/Submission/Types/StackInformation.php +++ b/seed/php-sdk/trace/src/Submission/Types/StackInformation.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StackInformation extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/StderrResponse.php b/seed/php-sdk/trace/src/Submission/Types/StderrResponse.php index 609269482ce..d888142b9fe 100644 --- a/seed/php-sdk/trace/src/Submission/Types/StderrResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/StderrResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StderrResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/StdoutResponse.php b/seed/php-sdk/trace/src/Submission/Types/StdoutResponse.php index 240da412ef7..0b812a44ce4 100644 --- a/seed/php-sdk/trace/src/Submission/Types/StdoutResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/StdoutResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StdoutResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/StopRequest.php b/seed/php-sdk/trace/src/Submission/Types/StopRequest.php index 8d9617095de..54f6377edfd 100644 --- a/seed/php-sdk/trace/src/Submission/Types/StopRequest.php +++ b/seed/php-sdk/trace/src/Submission/Types/StopRequest.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StopRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/StoppedResponse.php b/seed/php-sdk/trace/src/Submission/Types/StoppedResponse.php index 6d516c15b3f..747b742fed8 100644 --- a/seed/php-sdk/trace/src/Submission/Types/StoppedResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/StoppedResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class StoppedResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/SubmissionFileInfo.php b/seed/php-sdk/trace/src/Submission/Types/SubmissionFileInfo.php index e0bf54c3810..a7230f4cc5a 100644 --- a/seed/php-sdk/trace/src/Submission/Types/SubmissionFileInfo.php +++ b/seed/php-sdk/trace/src/Submission/Types/SubmissionFileInfo.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SubmissionFileInfo extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/SubmissionIdNotFound.php b/seed/php-sdk/trace/src/Submission/Types/SubmissionIdNotFound.php index a1c5bb1a074..77a9ba99e33 100644 --- a/seed/php-sdk/trace/src/Submission/Types/SubmissionIdNotFound.php +++ b/seed/php-sdk/trace/src/Submission/Types/SubmissionIdNotFound.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class SubmissionIdNotFound extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/SubmitRequestV2.php b/seed/php-sdk/trace/src/Submission/Types/SubmitRequestV2.php index a27d4b96a3a..db1f844e645 100644 --- a/seed/php-sdk/trace/src/Submission/Types/SubmitRequestV2.php +++ b/seed/php-sdk/trace/src/Submission/Types/SubmitRequestV2.php @@ -2,10 +2,10 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class SubmitRequestV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TerminatedResponse.php b/seed/php-sdk/trace/src/Submission/Types/TerminatedResponse.php index db5adc4536d..fb76b7e599c 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TerminatedResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/TerminatedResponse.php @@ -2,7 +2,7 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class TerminatedResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TestCaseHiddenGrade.php b/seed/php-sdk/trace/src/Submission/Types/TestCaseHiddenGrade.php index 6ec314c7dc2..f18f86a707b 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TestCaseHiddenGrade.php +++ b/seed/php-sdk/trace/src/Submission/Types/TestCaseHiddenGrade.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseHiddenGrade extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TestCaseNonHiddenGrade.php b/seed/php-sdk/trace/src/Submission/Types/TestCaseNonHiddenGrade.php index c319f9a3083..e2f9094d277 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TestCaseNonHiddenGrade.php +++ b/seed/php-sdk/trace/src/Submission/Types/TestCaseNonHiddenGrade.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseNonHiddenGrade extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TestCaseResult.php b/seed/php-sdk/trace/src/Submission/Types/TestCaseResult.php index f038f0f1647..be57b648425 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TestCaseResult.php +++ b/seed/php-sdk/trace/src/Submission/Types/TestCaseResult.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseResult extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TestCaseResultWithStdout.php b/seed/php-sdk/trace/src/Submission/Types/TestCaseResultWithStdout.php index b6c218a1f14..5b2d02d7d23 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TestCaseResultWithStdout.php +++ b/seed/php-sdk/trace/src/Submission/Types/TestCaseResultWithStdout.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseResultWithStdout extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TestSubmissionState.php b/seed/php-sdk/trace/src/Submission/Types/TestSubmissionState.php index c896b409526..290065d8022 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TestSubmissionState.php +++ b/seed/php-sdk/trace/src/Submission/Types/TestSubmissionState.php @@ -2,10 +2,10 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\TestCase; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class TestSubmissionState extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TestSubmissionStatusV2.php b/seed/php-sdk/trace/src/Submission/Types/TestSubmissionStatusV2.php index 062eb319d34..c4b3ca3ba6d 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TestSubmissionStatusV2.php +++ b/seed/php-sdk/trace/src/Submission/Types/TestSubmissionStatusV2.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; use Seed\V2\Problem\Types\ProblemInfoV2; class TestSubmissionStatusV2 extends SerializableType diff --git a/seed/php-sdk/trace/src/Submission/Types/TestSubmissionUpdate.php b/seed/php-sdk/trace/src/Submission/Types/TestSubmissionUpdate.php index d206417797b..7b37bc62604 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TestSubmissionUpdate.php +++ b/seed/php-sdk/trace/src/Submission/Types/TestSubmissionUpdate.php @@ -2,10 +2,10 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; -use Seed\Core\JsonProperty; -use Seed\Core\DateType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\DateType; class TestSubmissionUpdate extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TraceResponse.php b/seed/php-sdk/trace/src/Submission/Types/TraceResponse.php index 72513da405c..27fb12e3e73 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TraceResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/TraceResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TraceResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TraceResponseV2.php b/seed/php-sdk/trace/src/Submission/Types/TraceResponseV2.php index 17c80e82669..b45e11a580c 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TraceResponseV2.php +++ b/seed/php-sdk/trace/src/Submission/Types/TraceResponseV2.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TraceResponseV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TraceResponsesPage.php b/seed/php-sdk/trace/src/Submission/Types/TraceResponsesPage.php index 03fa3f84539..fe49996a628 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TraceResponsesPage.php +++ b/seed/php-sdk/trace/src/Submission/Types/TraceResponsesPage.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TraceResponsesPage extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TraceResponsesPageV2.php b/seed/php-sdk/trace/src/Submission/Types/TraceResponsesPageV2.php index b4efd028e4f..8d6451ca767 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TraceResponsesPageV2.php +++ b/seed/php-sdk/trace/src/Submission/Types/TraceResponsesPageV2.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TraceResponsesPageV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TracedFile.php b/seed/php-sdk/trace/src/Submission/Types/TracedFile.php index 415de5039c9..c28be777bc1 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TracedFile.php +++ b/seed/php-sdk/trace/src/Submission/Types/TracedFile.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TracedFile extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/TracedTestCase.php b/seed/php-sdk/trace/src/Submission/Types/TracedTestCase.php index bc4f66cb6c9..5e46eecc936 100644 --- a/seed/php-sdk/trace/src/Submission/Types/TracedTestCase.php +++ b/seed/php-sdk/trace/src/Submission/Types/TracedTestCase.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TracedTestCase extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/UnexpectedLanguageError.php b/seed/php-sdk/trace/src/Submission/Types/UnexpectedLanguageError.php index 2dec301c329..3b49b33bc2e 100644 --- a/seed/php-sdk/trace/src/Submission/Types/UnexpectedLanguageError.php +++ b/seed/php-sdk/trace/src/Submission/Types/UnexpectedLanguageError.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; +use Seed\Core\Json\JsonProperty; class UnexpectedLanguageError extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceFiles.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceFiles.php index 4a843ee1281..f7337d97ed1 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceFiles.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceFiles.php @@ -2,10 +2,10 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\FileInfo; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WorkspaceFiles extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceRanResponse.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceRanResponse.php index a38e5e6fae5..ede4cb6d109 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceRanResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceRanResponse.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WorkspaceRanResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceRunDetails.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceRunDetails.php index 860f3c87309..50263038226 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceRunDetails.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceRunDetails.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WorkspaceRunDetails extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceStarterFilesResponse.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceStarterFilesResponse.php index 7b874dea12a..1805e2935b4 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceStarterFilesResponse.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceStarterFilesResponse.php @@ -2,10 +2,10 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WorkspaceStarterFilesResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceStarterFilesResponseV2.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceStarterFilesResponseV2.php index 312b1e9fb23..aeed301e310 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceStarterFilesResponseV2.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceStarterFilesResponseV2.php @@ -2,11 +2,11 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; use Seed\V2\Problem\Types\Files; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WorkspaceStarterFilesResponseV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionState.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionState.php index 4ae6b4ed9fb..aa752910448 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionState.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionState.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WorkspaceSubmissionState extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionStatusV2.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionStatusV2.php index 835b18a8035..a48cb82fc79 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionStatusV2.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionStatusV2.php @@ -2,9 +2,9 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class WorkspaceSubmissionStatusV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionUpdate.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionUpdate.php index f8fd1f277c6..9623b777bfa 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionUpdate.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmissionUpdate.php @@ -2,10 +2,10 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use DateTime; -use Seed\Core\JsonProperty; -use Seed\Core\DateType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\DateType; class WorkspaceSubmissionUpdate extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmitRequest.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmitRequest.php index 5eb36eb6b43..51674d921e8 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmitRequest.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceSubmitRequest.php @@ -2,10 +2,10 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class WorkspaceSubmitRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/Submission/Types/WorkspaceTracedUpdate.php b/seed/php-sdk/trace/src/Submission/Types/WorkspaceTracedUpdate.php index 7f56b35f313..0f3aeeb083d 100644 --- a/seed/php-sdk/trace/src/Submission/Types/WorkspaceTracedUpdate.php +++ b/seed/php-sdk/trace/src/Submission/Types/WorkspaceTracedUpdate.php @@ -2,8 +2,8 @@ namespace Seed\Submission\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class WorkspaceTracedUpdate extends SerializableType { diff --git a/seed/php-sdk/trace/src/Sysprop/SyspropClient.php b/seed/php-sdk/trace/src/Sysprop/SyspropClient.php index 6c2dcda1e3e..3953e03d87e 100644 --- a/seed/php-sdk/trace/src/Sysprop/SyspropClient.php +++ b/seed/php-sdk/trace/src/Sysprop/SyspropClient.php @@ -2,15 +2,15 @@ namespace Seed\Sysprop; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Commons\Types\Language; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonDecoder; use JsonException; class SyspropClient diff --git a/seed/php-sdk/trace/src/V2/Problem/ProblemClient.php b/seed/php-sdk/trace/src/V2/Problem/ProblemClient.php index 02b15ab0607..c167c6c22f5 100644 --- a/seed/php-sdk/trace/src/V2/Problem/ProblemClient.php +++ b/seed/php-sdk/trace/src/V2/Problem/ProblemClient.php @@ -2,14 +2,14 @@ namespace Seed\V2\Problem; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\V2\Problem\Types\LightweightProblemInfoV2; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\V2\Problem\Types\ProblemInfoV2; diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/BasicCustomFiles.php b/seed/php-sdk/trace/src/V2/Problem/Types/BasicCustomFiles.php index e968d069687..0b7d8ed7f79 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/BasicCustomFiles.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/BasicCustomFiles.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class BasicCustomFiles extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/BasicTestCaseTemplate.php b/seed/php-sdk/trace/src/V2/Problem/Types/BasicTestCaseTemplate.php index c4a26e9f350..22cb87e9fa0 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/BasicTestCaseTemplate.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/BasicTestCaseTemplate.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BasicTestCaseTemplate extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/CreateProblemRequestV2.php b/seed/php-sdk/trace/src/V2/Problem/Types/CreateProblemRequestV2.php index 0b80969c3b4..da33828c43f 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/CreateProblemRequestV2.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/CreateProblemRequestV2.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Problem\Types\ProblemDescription; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Commons\Types\Language; class CreateProblemRequestV2 extends SerializableType diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/DeepEqualityCorrectnessCheck.php b/seed/php-sdk/trace/src/V2/Problem/Types/DeepEqualityCorrectnessCheck.php index 9ddfae8a231..f5e6986becf 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/DeepEqualityCorrectnessCheck.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/DeepEqualityCorrectnessCheck.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DeepEqualityCorrectnessCheck extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/DefaultProvidedFile.php b/seed/php-sdk/trace/src/V2/Problem/Types/DefaultProvidedFile.php index aef682f80f3..1579109ff39 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/DefaultProvidedFile.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/DefaultProvidedFile.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DefaultProvidedFile extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/FileInfoV2.php b/seed/php-sdk/trace/src/V2/Problem/Types/FileInfoV2.php index 5c10aaab9d5..82189e3192f 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/FileInfoV2.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/FileInfoV2.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FileInfoV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/Files.php b/seed/php-sdk/trace/src/V2/Problem/Types/Files.php index 5212afcd94e..4807a8b478a 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/Files.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/Files.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Files extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/FunctionImplementation.php b/seed/php-sdk/trace/src/V2/Problem/Types/FunctionImplementation.php index cfc9205970c..d75bb991657 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/FunctionImplementation.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/FunctionImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FunctionImplementation extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/FunctionImplementationForMultipleLanguages.php b/seed/php-sdk/trace/src/V2/Problem/Types/FunctionImplementationForMultipleLanguages.php index 61631bf3739..51e7d757cae 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/FunctionImplementationForMultipleLanguages.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/FunctionImplementationForMultipleLanguages.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class FunctionImplementationForMultipleLanguages extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/GeneratedFiles.php b/seed/php-sdk/trace/src/V2/Problem/Types/GeneratedFiles.php index 19b8c162444..f5e99268dd6 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/GeneratedFiles.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/GeneratedFiles.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GeneratedFiles extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/GetBasicSolutionFileRequest.php b/seed/php-sdk/trace/src/V2/Problem/Types/GetBasicSolutionFileRequest.php index 105d5102b5f..71742e774d5 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/GetBasicSolutionFileRequest.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/GetBasicSolutionFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetBasicSolutionFileRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/GetBasicSolutionFileResponse.php b/seed/php-sdk/trace/src/V2/Problem/Types/GetBasicSolutionFileResponse.php index 9005e3bb6fb..1a22274cab4 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/GetBasicSolutionFileResponse.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/GetBasicSolutionFileResponse.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetBasicSolutionFileResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/GetFunctionSignatureRequest.php b/seed/php-sdk/trace/src/V2/Problem/Types/GetFunctionSignatureRequest.php index 47291a67949..f6e095658af 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/GetFunctionSignatureRequest.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/GetFunctionSignatureRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetFunctionSignatureRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/GetFunctionSignatureResponse.php b/seed/php-sdk/trace/src/V2/Problem/Types/GetFunctionSignatureResponse.php index 6fbfd3f5a5b..68058cda68c 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/GetFunctionSignatureResponse.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/GetFunctionSignatureResponse.php @@ -2,10 +2,10 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetFunctionSignatureResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/GetGeneratedTestCaseFileRequest.php b/seed/php-sdk/trace/src/V2/Problem/Types/GetGeneratedTestCaseFileRequest.php index 97a9f0898c0..14f1fa208b1 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/GetGeneratedTestCaseFileRequest.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/GetGeneratedTestCaseFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetGeneratedTestCaseFileRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/GetGeneratedTestCaseTemplateFileRequest.php b/seed/php-sdk/trace/src/V2/Problem/Types/GetGeneratedTestCaseTemplateFileRequest.php index 037d6580150..2a6d0fdfe9e 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/GetGeneratedTestCaseTemplateFileRequest.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/GetGeneratedTestCaseTemplateFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetGeneratedTestCaseTemplateFileRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/LightweightProblemInfoV2.php b/seed/php-sdk/trace/src/V2/Problem/Types/LightweightProblemInfoV2.php index 9b498395a86..4316fefcef1 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/LightweightProblemInfoV2.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/LightweightProblemInfoV2.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class LightweightProblemInfoV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/NonVoidFunctionDefinition.php b/seed/php-sdk/trace/src/V2/Problem/Types/NonVoidFunctionDefinition.php index 114dee8be27..070e86faaa6 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/NonVoidFunctionDefinition.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/NonVoidFunctionDefinition.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NonVoidFunctionDefinition extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/NonVoidFunctionSignature.php b/seed/php-sdk/trace/src/V2/Problem/Types/NonVoidFunctionSignature.php index c380df8edd7..869af73c62c 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/NonVoidFunctionSignature.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/NonVoidFunctionSignature.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class NonVoidFunctionSignature extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/Parameter.php b/seed/php-sdk/trace/src/V2/Problem/Types/Parameter.php index e1065322835..7bd5cc0245d 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/Parameter.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/Parameter.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Parameter extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/ProblemInfoV2.php b/seed/php-sdk/trace/src/V2/Problem/Types/ProblemInfoV2.php index e96a403a30d..d01aa326a85 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/ProblemInfoV2.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/ProblemInfoV2.php @@ -2,11 +2,11 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Problem\Types\ProblemDescription; use Seed\Commons\Types\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class ProblemInfoV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseExpects.php b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseExpects.php index e52a091b34f..3158f61d93a 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseExpects.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseExpects.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseExpects extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseImplementation.php b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseImplementation.php index 56da04b5dbe..4c0e59f7a31 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseImplementation.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseImplementation extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseImplementationDescription.php b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseImplementationDescription.php index e220ab458e0..78d4644bfda 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseImplementationDescription.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseImplementationDescription.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCaseImplementationDescription extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseMetadata.php b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseMetadata.php index bf089fe2f8e..ab52006cc91 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseMetadata.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseMetadata.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseMetadata extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseTemplate.php b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseTemplate.php index 710c1b9d5b9..af2f4dc3dd4 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseTemplate.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseTemplate.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseTemplate extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseV2.php b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseV2.php index 7ae88d7818a..63621fa16f2 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseV2.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseV2.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCaseV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseWithActualResultImplementation.php b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseWithActualResultImplementation.php index 5235aad7363..fbe1a29f4be 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseWithActualResultImplementation.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/TestCaseWithActualResultImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseWithActualResultImplementation extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionDefinition.php b/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionDefinition.php index 7db7fb537c9..a1685568db9 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionDefinition.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionDefinition.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionDefinition extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionDefinitionThatTakesActualResult.php b/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionDefinitionThatTakesActualResult.php index 57751f79891..256e761dc70 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionDefinitionThatTakesActualResult.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionDefinitionThatTakesActualResult.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; /** * The generated signature will include an additional param, actualResult diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionSignature.php b/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionSignature.php index 5c281d7525d..a89da6e85b5 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionSignature.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionSignature.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionSignature extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionSignatureThatTakesActualResult.php b/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionSignatureThatTakesActualResult.php index 91ac28151a8..d0bcb9d2704 100644 --- a/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionSignatureThatTakesActualResult.php +++ b/seed/php-sdk/trace/src/V2/Problem/Types/VoidFunctionSignatureThatTakesActualResult.php @@ -2,9 +2,9 @@ namespace Seed\V2\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionSignatureThatTakesActualResult extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V2Client.php b/seed/php-sdk/trace/src/V2/V2Client.php index 474d307eeb8..7f292bd983c 100644 --- a/seed/php-sdk/trace/src/V2/V2Client.php +++ b/seed/php-sdk/trace/src/V2/V2Client.php @@ -4,12 +4,12 @@ use Seed\V2\Problem\ProblemClient; use Seed\V2\V3\V3Client; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class V2Client diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/ProblemClient.php b/seed/php-sdk/trace/src/V2/V3/Problem/ProblemClient.php index 1411cde9476..366b0ca6459 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/ProblemClient.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/ProblemClient.php @@ -2,14 +2,14 @@ namespace Seed\V2\V3\Problem; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\V2\V3\Problem\Types\LightweightProblemInfoV2; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; +use Seed\Core\Json\JsonApiRequest; use Seed\Environments; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\V2\V3\Problem\Types\ProblemInfoV2; diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/BasicCustomFiles.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/BasicCustomFiles.php index 3e41918551f..f17b24ca35d 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/BasicCustomFiles.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/BasicCustomFiles.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Commons\Types\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class BasicCustomFiles extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/BasicTestCaseTemplate.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/BasicTestCaseTemplate.php index 2d752a21a0c..e6ba6e77bb5 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/BasicTestCaseTemplate.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/BasicTestCaseTemplate.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class BasicTestCaseTemplate extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/CreateProblemRequestV2.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/CreateProblemRequestV2.php index cb4e3d5baf7..b3565696f2f 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/CreateProblemRequestV2.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/CreateProblemRequestV2.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Problem\Types\ProblemDescription; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; use Seed\Commons\Types\Language; class CreateProblemRequestV2 extends SerializableType diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/DeepEqualityCorrectnessCheck.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/DeepEqualityCorrectnessCheck.php index 0159d471e17..5f5ff3e2901 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/DeepEqualityCorrectnessCheck.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/DeepEqualityCorrectnessCheck.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class DeepEqualityCorrectnessCheck extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/DefaultProvidedFile.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/DefaultProvidedFile.php index c424f3ecc3d..1de29881e86 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/DefaultProvidedFile.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/DefaultProvidedFile.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class DefaultProvidedFile extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/FileInfoV2.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/FileInfoV2.php index 58e6ce20936..d636fca36c8 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/FileInfoV2.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/FileInfoV2.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FileInfoV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/Files.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/Files.php index 8219579dcf5..578d56a607b 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/Files.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/Files.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class Files extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/FunctionImplementation.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/FunctionImplementation.php index 0c12b7aa767..1ab0c9407ff 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/FunctionImplementation.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/FunctionImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class FunctionImplementation extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/FunctionImplementationForMultipleLanguages.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/FunctionImplementationForMultipleLanguages.php index 0611d9d1aa8..c995f18eda9 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/FunctionImplementationForMultipleLanguages.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/FunctionImplementationForMultipleLanguages.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class FunctionImplementationForMultipleLanguages extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GeneratedFiles.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GeneratedFiles.php index f2112ba2a7f..61f3bb1fa83 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GeneratedFiles.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GeneratedFiles.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GeneratedFiles extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetBasicSolutionFileRequest.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetBasicSolutionFileRequest.php index 25a012de733..4ecbadcbf44 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetBasicSolutionFileRequest.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetBasicSolutionFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetBasicSolutionFileRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetBasicSolutionFileResponse.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetBasicSolutionFileResponse.php index 582817d4680..a0e16177940 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetBasicSolutionFileResponse.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetBasicSolutionFileResponse.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetBasicSolutionFileResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetFunctionSignatureRequest.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetFunctionSignatureRequest.php index 35b169fe58e..03e1fe7dccf 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetFunctionSignatureRequest.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetFunctionSignatureRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetFunctionSignatureRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetFunctionSignatureResponse.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetFunctionSignatureResponse.php index 7f1fc13cb01..447147fba4a 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetFunctionSignatureResponse.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetFunctionSignatureResponse.php @@ -2,10 +2,10 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; use Seed\Commons\Types\Language; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class GetFunctionSignatureResponse extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetGeneratedTestCaseFileRequest.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetGeneratedTestCaseFileRequest.php index 7b14258b145..4d9b22cf50f 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetGeneratedTestCaseFileRequest.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetGeneratedTestCaseFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetGeneratedTestCaseFileRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetGeneratedTestCaseTemplateFileRequest.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetGeneratedTestCaseTemplateFileRequest.php index 6c668bd3f93..6189ef4ae69 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetGeneratedTestCaseTemplateFileRequest.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/GetGeneratedTestCaseTemplateFileRequest.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetGeneratedTestCaseTemplateFileRequest extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/LightweightProblemInfoV2.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/LightweightProblemInfoV2.php index 0103cca842c..83bccb28525 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/LightweightProblemInfoV2.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/LightweightProblemInfoV2.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class LightweightProblemInfoV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/NonVoidFunctionDefinition.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/NonVoidFunctionDefinition.php index 0dcb0889fa2..cc13bf2abbb 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/NonVoidFunctionDefinition.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/NonVoidFunctionDefinition.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class NonVoidFunctionDefinition extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/NonVoidFunctionSignature.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/NonVoidFunctionSignature.php index 32490e6851e..d66df5e766b 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/NonVoidFunctionSignature.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/NonVoidFunctionSignature.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class NonVoidFunctionSignature extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/Parameter.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/Parameter.php index e6c94e8e656..14c3bacda87 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/Parameter.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/Parameter.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Parameter extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/ProblemInfoV2.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/ProblemInfoV2.php index 99b33e9ae79..76c597c536f 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/ProblemInfoV2.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/ProblemInfoV2.php @@ -2,11 +2,11 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Problem\Types\ProblemDescription; use Seed\Commons\Types\Language; -use Seed\Core\ArrayType; +use Seed\Core\Types\ArrayType; class ProblemInfoV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseExpects.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseExpects.php index 17edf4987ea..171cc0896a0 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseExpects.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseExpects.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseExpects extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseImplementation.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseImplementation.php index ee9b57fd906..78640a8b85b 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseImplementation.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseImplementation extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseImplementationDescription.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseImplementationDescription.php index 89643b8bd6a..242c1e50751 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseImplementationDescription.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseImplementationDescription.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCaseImplementationDescription extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseMetadata.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseMetadata.php index 98fc1e72019..a75d98b8f1d 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseMetadata.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseMetadata.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseMetadata extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseTemplate.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseTemplate.php index 89c5479348a..49d6f9f5c1b 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseTemplate.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseTemplate.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseTemplate extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseV2.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseV2.php index 8dc3e1fbfbb..9f24f4a0398 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseV2.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseV2.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class TestCaseV2 extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseWithActualResultImplementation.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseWithActualResultImplementation.php index 2d3bea1b83b..2f44ad9a260 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseWithActualResultImplementation.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/TestCaseWithActualResultImplementation.php @@ -2,8 +2,8 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class TestCaseWithActualResultImplementation extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionDefinition.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionDefinition.php index e226ccaa86a..40b98be8098 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionDefinition.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionDefinition.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionDefinition extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionDefinitionThatTakesActualResult.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionDefinitionThatTakesActualResult.php index ce4a0f368ee..adb4f8ec64d 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionDefinitionThatTakesActualResult.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionDefinitionThatTakesActualResult.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; /** * The generated signature will include an additional param, actualResult diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionSignature.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionSignature.php index 0bdd9daf49a..400fc8b971e 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionSignature.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionSignature.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionSignature extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionSignatureThatTakesActualResult.php b/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionSignatureThatTakesActualResult.php index c4cb43394d8..0b7b87fc7b1 100644 --- a/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionSignatureThatTakesActualResult.php +++ b/seed/php-sdk/trace/src/V2/V3/Problem/Types/VoidFunctionSignatureThatTakesActualResult.php @@ -2,9 +2,9 @@ namespace Seed\V2\V3\Problem\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; -use Seed\Core\ArrayType; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; +use Seed\Core\Types\ArrayType; class VoidFunctionSignatureThatTakesActualResult extends SerializableType { diff --git a/seed/php-sdk/trace/src/V2/V3/V3Client.php b/seed/php-sdk/trace/src/V2/V3/V3Client.php index cf09e6dba57..bd440b7baad 100644 --- a/seed/php-sdk/trace/src/V2/V3/V3Client.php +++ b/seed/php-sdk/trace/src/V2/V3/V3Client.php @@ -3,7 +3,7 @@ namespace Seed\V2\V3; use Seed\V2\V3\Problem\ProblemClient; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class V3Client { diff --git a/seed/php-sdk/trace/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/trace/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/trace/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/EnumTest.php b/seed/php-sdk/trace/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/trace/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/trace/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/trace/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/trace/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/trace/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/trace/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/trace/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/trace/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/ArrayType.php b/seed/php-sdk/undiscriminated-unions/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/BaseApiRequest.php b/seed/php-sdk/undiscriminated-unions/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/undiscriminated-unions/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Client/HttpMethod.php b/seed/php-sdk/undiscriminated-unions/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Constant.php b/seed/php-sdk/undiscriminated-unions/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonDecoder.php b/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonEncoder.php b/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Json/SerializableType.php b/seed/php-sdk/undiscriminated-unions/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Json/Utils.php b/seed/php-sdk/undiscriminated-unions/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/JsonApiRequest.php b/seed/php-sdk/undiscriminated-unions/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/JsonDecoder.php b/seed/php-sdk/undiscriminated-unions/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/JsonDeserializer.php b/seed/php-sdk/undiscriminated-unions/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/JsonEncoder.php b/seed/php-sdk/undiscriminated-unions/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/RawClient.php b/seed/php-sdk/undiscriminated-unions/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/SerializableType.php b/seed/php-sdk/undiscriminated-unions/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Types/ArrayType.php b/seed/php-sdk/undiscriminated-unions/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Types/Constant.php b/seed/php-sdk/undiscriminated-unions/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Union.php b/seed/php-sdk/undiscriminated-unions/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/Core/Utils.php b/seed/php-sdk/undiscriminated-unions/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/undiscriminated-unions/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/undiscriminated-unions/src/SeedClient.php b/seed/php-sdk/undiscriminated-unions/src/SeedClient.php index e515ceb7dc1..e03af6b0b81 100644 --- a/seed/php-sdk/undiscriminated-unions/src/SeedClient.php +++ b/seed/php-sdk/undiscriminated-unions/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Union\UnionClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/undiscriminated-unions/src/Union/UnionClient.php b/seed/php-sdk/undiscriminated-unions/src/Union/UnionClient.php index 035279c7d31..c630d94fb05 100644 --- a/seed/php-sdk/undiscriminated-unions/src/Union/UnionClient.php +++ b/seed/php-sdk/undiscriminated-unions/src/Union/UnionClient.php @@ -2,14 +2,14 @@ namespace Seed\Union; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonSerializer; -use Seed\Core\Union; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonSerializer; +use Seed\Core\Types\Union; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Union\Types\KeyType; diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/EnumTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/undiscriminated-unions/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/unions/src/Core/ArrayType.php b/seed/php-sdk/unions/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/unions/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/unions/src/Core/BaseApiRequest.php b/seed/php-sdk/unions/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/unions/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/unions/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/unions/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/unions/src/Core/Client/HttpMethod.php b/seed/php-sdk/unions/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/unions/src/Core/Constant.php b/seed/php-sdk/unions/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/unions/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/unions/src/Core/Json/JsonDecoder.php b/seed/php-sdk/unions/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/unions/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/unions/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/unions/src/Core/Json/JsonEncoder.php b/seed/php-sdk/unions/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/unions/src/Core/Json/SerializableType.php b/seed/php-sdk/unions/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/unions/src/Core/Json/Utils.php b/seed/php-sdk/unions/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/unions/src/Core/JsonApiRequest.php b/seed/php-sdk/unions/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/unions/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/unions/src/Core/JsonDecoder.php b/seed/php-sdk/unions/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/unions/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/unions/src/Core/JsonDeserializer.php b/seed/php-sdk/unions/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/unions/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/unions/src/Core/JsonEncoder.php b/seed/php-sdk/unions/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/unions/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/unions/src/Core/RawClient.php b/seed/php-sdk/unions/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/unions/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/unions/src/Core/SerializableType.php b/seed/php-sdk/unions/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/unions/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/unions/src/Core/Types/ArrayType.php b/seed/php-sdk/unions/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/unions/src/Core/Types/Constant.php b/seed/php-sdk/unions/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/unions/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/unions/src/Core/Union.php b/seed/php-sdk/unions/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/unions/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/unions/src/Core/Utils.php b/seed/php-sdk/unions/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/unions/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/unions/src/SeedClient.php b/seed/php-sdk/unions/src/SeedClient.php index e515ceb7dc1..e03af6b0b81 100644 --- a/seed/php-sdk/unions/src/SeedClient.php +++ b/seed/php-sdk/unions/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Union\UnionClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/unions/src/Types/Types/Bar.php b/seed/php-sdk/unions/src/Types/Types/Bar.php index 5449432409d..b699d2d4329 100644 --- a/seed/php-sdk/unions/src/Types/Types/Bar.php +++ b/seed/php-sdk/unions/src/Types/Types/Bar.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Bar extends SerializableType { diff --git a/seed/php-sdk/unions/src/Types/Types/Foo.php b/seed/php-sdk/unions/src/Types/Types/Foo.php index 213671ae1dc..c28b4e15526 100644 --- a/seed/php-sdk/unions/src/Types/Types/Foo.php +++ b/seed/php-sdk/unions/src/Types/Types/Foo.php @@ -2,8 +2,8 @@ namespace Seed\Types\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Foo extends SerializableType { diff --git a/seed/php-sdk/unions/src/Union/Types/Circle.php b/seed/php-sdk/unions/src/Union/Types/Circle.php index 98a0c52fea9..f3b0b2205fa 100644 --- a/seed/php-sdk/unions/src/Union/Types/Circle.php +++ b/seed/php-sdk/unions/src/Union/Types/Circle.php @@ -2,8 +2,8 @@ namespace Seed\Union\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Circle extends SerializableType { diff --git a/seed/php-sdk/unions/src/Union/Types/GetShapeRequest.php b/seed/php-sdk/unions/src/Union/Types/GetShapeRequest.php index 3f391c1b3b7..96443a08776 100644 --- a/seed/php-sdk/unions/src/Union/Types/GetShapeRequest.php +++ b/seed/php-sdk/unions/src/Union/Types/GetShapeRequest.php @@ -2,8 +2,8 @@ namespace Seed\Union\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class GetShapeRequest extends SerializableType { diff --git a/seed/php-sdk/unions/src/Union/Types/Square.php b/seed/php-sdk/unions/src/Union/Types/Square.php index b0ac1608804..5f4ee9fe24f 100644 --- a/seed/php-sdk/unions/src/Union/Types/Square.php +++ b/seed/php-sdk/unions/src/Union/Types/Square.php @@ -2,8 +2,8 @@ namespace Seed\Union\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class Square extends SerializableType { diff --git a/seed/php-sdk/unions/src/Union/UnionClient.php b/seed/php-sdk/unions/src/Union/UnionClient.php index 4c30c93f47e..70e8f419c9b 100644 --- a/seed/php-sdk/unions/src/Union/UnionClient.php +++ b/seed/php-sdk/unions/src/Union/UnionClient.php @@ -2,12 +2,12 @@ namespace Seed\Union; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/unions/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/unions/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/unions/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/EnumTest.php b/seed/php-sdk/unions/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/unions/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/unions/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/unions/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/unions/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/unions/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/unions/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/unions/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/unions/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/unknown/src/Core/ArrayType.php b/seed/php-sdk/unknown/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/unknown/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/unknown/src/Core/BaseApiRequest.php b/seed/php-sdk/unknown/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/unknown/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/unknown/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/unknown/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/unknown/src/Core/Client/HttpMethod.php b/seed/php-sdk/unknown/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/unknown/src/Core/Constant.php b/seed/php-sdk/unknown/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/unknown/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/unknown/src/Core/Json/JsonDecoder.php b/seed/php-sdk/unknown/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/unknown/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/unknown/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/unknown/src/Core/Json/JsonEncoder.php b/seed/php-sdk/unknown/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/unknown/src/Core/Json/SerializableType.php b/seed/php-sdk/unknown/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/unknown/src/Core/Json/Utils.php b/seed/php-sdk/unknown/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/unknown/src/Core/JsonApiRequest.php b/seed/php-sdk/unknown/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/unknown/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/unknown/src/Core/JsonDecoder.php b/seed/php-sdk/unknown/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/unknown/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/unknown/src/Core/JsonDeserializer.php b/seed/php-sdk/unknown/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/unknown/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/unknown/src/Core/JsonEncoder.php b/seed/php-sdk/unknown/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/unknown/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/unknown/src/Core/RawClient.php b/seed/php-sdk/unknown/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/unknown/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/unknown/src/Core/SerializableType.php b/seed/php-sdk/unknown/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/unknown/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/unknown/src/Core/Types/ArrayType.php b/seed/php-sdk/unknown/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/unknown/src/Core/Types/Constant.php b/seed/php-sdk/unknown/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/unknown/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/unknown/src/Core/Union.php b/seed/php-sdk/unknown/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/unknown/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/unknown/src/Core/Utils.php b/seed/php-sdk/unknown/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/unknown/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/unknown/src/SeedClient.php b/seed/php-sdk/unknown/src/SeedClient.php index a58f3150075..f201c990c59 100644 --- a/seed/php-sdk/unknown/src/SeedClient.php +++ b/seed/php-sdk/unknown/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Unknown\UnknownClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/unknown/src/Unknown/Types/MyObject.php b/seed/php-sdk/unknown/src/Unknown/Types/MyObject.php index ce54d5c312f..f6c06173b11 100644 --- a/seed/php-sdk/unknown/src/Unknown/Types/MyObject.php +++ b/seed/php-sdk/unknown/src/Unknown/Types/MyObject.php @@ -2,8 +2,8 @@ namespace Seed\Unknown\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class MyObject extends SerializableType { diff --git a/seed/php-sdk/unknown/src/Unknown/UnknownClient.php b/seed/php-sdk/unknown/src/Unknown/UnknownClient.php index a27f94aa45b..9a9f56725a3 100644 --- a/seed/php-sdk/unknown/src/Unknown/UnknownClient.php +++ b/seed/php-sdk/unknown/src/Unknown/UnknownClient.php @@ -2,12 +2,12 @@ namespace Seed\Unknown; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; -use Seed\Core\JsonDecoder; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; +use Seed\Core\Json\JsonDecoder; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Unknown\Types\MyObject; diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/unknown/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/EnumTest.php b/seed/php-sdk/unknown/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/unknown/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/unknown/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/unknown/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/unknown/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/unknown/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/unknown/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/unknown/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/validation/src/Core/ArrayType.php b/seed/php-sdk/validation/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/validation/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/validation/src/Core/BaseApiRequest.php b/seed/php-sdk/validation/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/validation/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/validation/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/validation/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/validation/src/Core/Client/HttpMethod.php b/seed/php-sdk/validation/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/validation/src/Core/Constant.php b/seed/php-sdk/validation/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/validation/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/validation/src/Core/Json/JsonDecoder.php b/seed/php-sdk/validation/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/validation/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/validation/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/validation/src/Core/Json/JsonEncoder.php b/seed/php-sdk/validation/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/validation/src/Core/Json/SerializableType.php b/seed/php-sdk/validation/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/validation/src/Core/Json/Utils.php b/seed/php-sdk/validation/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/validation/src/Core/JsonApiRequest.php b/seed/php-sdk/validation/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/validation/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/validation/src/Core/JsonDecoder.php b/seed/php-sdk/validation/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/validation/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/validation/src/Core/JsonDeserializer.php b/seed/php-sdk/validation/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/validation/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/validation/src/Core/JsonEncoder.php b/seed/php-sdk/validation/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/validation/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/validation/src/Core/RawClient.php b/seed/php-sdk/validation/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/validation/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/validation/src/Core/SerializableType.php b/seed/php-sdk/validation/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/validation/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/validation/src/Core/Types/ArrayType.php b/seed/php-sdk/validation/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/validation/src/Core/Types/Constant.php b/seed/php-sdk/validation/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/validation/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/validation/src/Core/Union.php b/seed/php-sdk/validation/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/validation/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/validation/src/Core/Utils.php b/seed/php-sdk/validation/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/validation/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/validation/src/Requests/CreateRequest.php b/seed/php-sdk/validation/src/Requests/CreateRequest.php index 271e135d79c..866e0d48a79 100644 --- a/seed/php-sdk/validation/src/Requests/CreateRequest.php +++ b/seed/php-sdk/validation/src/Requests/CreateRequest.php @@ -2,8 +2,8 @@ namespace Seed\Requests; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; use Seed\Types\Shape; class CreateRequest extends SerializableType diff --git a/seed/php-sdk/validation/src/Requests/GetRequest.php b/seed/php-sdk/validation/src/Requests/GetRequest.php index 0acb71ca401..34012c7cf99 100644 --- a/seed/php-sdk/validation/src/Requests/GetRequest.php +++ b/seed/php-sdk/validation/src/Requests/GetRequest.php @@ -2,7 +2,7 @@ namespace Seed\Requests; -use Seed\Core\SerializableType; +use Seed\Core\Json\SerializableType; class GetRequest extends SerializableType { diff --git a/seed/php-sdk/validation/src/SeedClient.php b/seed/php-sdk/validation/src/SeedClient.php index 2d175882f3e..e90c406fcbb 100644 --- a/seed/php-sdk/validation/src/SeedClient.php +++ b/seed/php-sdk/validation/src/SeedClient.php @@ -3,13 +3,13 @@ namespace Seed; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Requests\CreateRequest; use Seed\Types\Type; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; use Seed\Requests\GetRequest; diff --git a/seed/php-sdk/validation/src/Types/Type.php b/seed/php-sdk/validation/src/Types/Type.php index 1012bdca460..5eb3eb93f0d 100644 --- a/seed/php-sdk/validation/src/Types/Type.php +++ b/seed/php-sdk/validation/src/Types/Type.php @@ -2,8 +2,8 @@ namespace Seed\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; /** * Defines properties with default values and validation rules. diff --git a/seed/php-sdk/validation/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/validation/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/validation/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/EnumTest.php b/seed/php-sdk/validation/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/validation/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/validation/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/validation/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/validation/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/validation/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/validation/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/validation/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/validation/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/variables/src/Core/ArrayType.php b/seed/php-sdk/variables/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/variables/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/variables/src/Core/BaseApiRequest.php b/seed/php-sdk/variables/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/variables/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/variables/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/variables/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/variables/src/Core/Client/HttpMethod.php b/seed/php-sdk/variables/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/variables/src/Core/Constant.php b/seed/php-sdk/variables/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/variables/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/variables/src/Core/Json/JsonDecoder.php b/seed/php-sdk/variables/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/variables/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/variables/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/variables/src/Core/Json/JsonEncoder.php b/seed/php-sdk/variables/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/variables/src/Core/Json/SerializableType.php b/seed/php-sdk/variables/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/variables/src/Core/Json/Utils.php b/seed/php-sdk/variables/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/variables/src/Core/JsonApiRequest.php b/seed/php-sdk/variables/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/variables/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/variables/src/Core/JsonDecoder.php b/seed/php-sdk/variables/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/variables/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/variables/src/Core/JsonDeserializer.php b/seed/php-sdk/variables/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/variables/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/variables/src/Core/JsonEncoder.php b/seed/php-sdk/variables/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/variables/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/variables/src/Core/RawClient.php b/seed/php-sdk/variables/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/variables/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/variables/src/Core/SerializableType.php b/seed/php-sdk/variables/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/variables/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/variables/src/Core/Types/ArrayType.php b/seed/php-sdk/variables/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/variables/src/Core/Types/Constant.php b/seed/php-sdk/variables/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/variables/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/variables/src/Core/Union.php b/seed/php-sdk/variables/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/variables/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/variables/src/Core/Utils.php b/seed/php-sdk/variables/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/variables/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/variables/src/SeedClient.php b/seed/php-sdk/variables/src/SeedClient.php index 5f052555c44..4ca47b660f7 100644 --- a/seed/php-sdk/variables/src/SeedClient.php +++ b/seed/php-sdk/variables/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\Service\ServiceClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/variables/src/Service/ServiceClient.php b/seed/php-sdk/variables/src/Service/ServiceClient.php index ce059eee24f..dc6fc68c2a2 100644 --- a/seed/php-sdk/variables/src/Service/ServiceClient.php +++ b/seed/php-sdk/variables/src/Service/ServiceClient.php @@ -2,11 +2,11 @@ namespace Seed\Service; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use Psr\Http\Client\ClientExceptionInterface; class ServiceClient diff --git a/seed/php-sdk/variables/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/variables/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/variables/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/EnumTest.php b/seed/php-sdk/variables/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/variables/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/variables/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/variables/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/variables/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/variables/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/variables/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/variables/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/variables/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/ArrayType.php b/seed/php-sdk/version-no-default/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/BaseApiRequest.php b/seed/php-sdk/version-no-default/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/version-no-default/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Client/HttpMethod.php b/seed/php-sdk/version-no-default/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Constant.php b/seed/php-sdk/version-no-default/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Json/JsonDecoder.php b/seed/php-sdk/version-no-default/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/version-no-default/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Json/JsonEncoder.php b/seed/php-sdk/version-no-default/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Json/SerializableType.php b/seed/php-sdk/version-no-default/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Json/Utils.php b/seed/php-sdk/version-no-default/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/JsonApiRequest.php b/seed/php-sdk/version-no-default/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/JsonDecoder.php b/seed/php-sdk/version-no-default/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/JsonDeserializer.php b/seed/php-sdk/version-no-default/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/JsonEncoder.php b/seed/php-sdk/version-no-default/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/RawClient.php b/seed/php-sdk/version-no-default/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/SerializableType.php b/seed/php-sdk/version-no-default/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/Types/ArrayType.php b/seed/php-sdk/version-no-default/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Types/Constant.php b/seed/php-sdk/version-no-default/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/version-no-default/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/version-no-default/src/Core/Union.php b/seed/php-sdk/version-no-default/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/version-no-default/src/Core/Utils.php b/seed/php-sdk/version-no-default/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/version-no-default/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/version-no-default/src/SeedClient.php b/seed/php-sdk/version-no-default/src/SeedClient.php index be7d76e3425..b69d9150d63 100644 --- a/seed/php-sdk/version-no-default/src/SeedClient.php +++ b/seed/php-sdk/version-no-default/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\User\UserClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/version-no-default/src/User/Types/User.php b/seed/php-sdk/version-no-default/src/User/Types/User.php index af657430e5c..5c95ec2354c 100644 --- a/seed/php-sdk/version-no-default/src/User/Types/User.php +++ b/seed/php-sdk/version-no-default/src/User/Types/User.php @@ -2,8 +2,8 @@ namespace Seed\User\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-sdk/version-no-default/src/User/UserClient.php b/seed/php-sdk/version-no-default/src/User/UserClient.php index ed192c1f300..b93ac493fba 100644 --- a/seed/php-sdk/version-no-default/src/User/UserClient.php +++ b/seed/php-sdk/version-no-default/src/User/UserClient.php @@ -2,12 +2,12 @@ namespace Seed\User; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\User\Types\User; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/EnumTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/version-no-default/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/version-no-default/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/version/src/Core/ArrayType.php b/seed/php-sdk/version/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/version/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/version/src/Core/BaseApiRequest.php b/seed/php-sdk/version/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/version/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/version/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/version/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/version/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/version/src/Core/Client/HttpMethod.php b/seed/php-sdk/version/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/version/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/version/src/Core/Constant.php b/seed/php-sdk/version/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/version/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/version/src/Core/Json/JsonDecoder.php b/seed/php-sdk/version/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/version/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/version/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/version/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/version/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/version/src/Core/Json/JsonEncoder.php b/seed/php-sdk/version/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/version/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/version/src/Core/Json/SerializableType.php b/seed/php-sdk/version/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/version/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/version/src/Core/Json/Utils.php b/seed/php-sdk/version/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/version/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/version/src/Core/JsonApiRequest.php b/seed/php-sdk/version/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/version/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/version/src/Core/JsonDecoder.php b/seed/php-sdk/version/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/version/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/version/src/Core/JsonDeserializer.php b/seed/php-sdk/version/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/version/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/version/src/Core/JsonEncoder.php b/seed/php-sdk/version/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/version/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/version/src/Core/RawClient.php b/seed/php-sdk/version/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/version/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/version/src/Core/SerializableType.php b/seed/php-sdk/version/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/version/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/version/src/Core/Types/ArrayType.php b/seed/php-sdk/version/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/version/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/version/src/Core/Types/Constant.php b/seed/php-sdk/version/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/version/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/version/src/Core/Union.php b/seed/php-sdk/version/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/version/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/version/src/Core/Utils.php b/seed/php-sdk/version/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/version/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/version/src/SeedClient.php b/seed/php-sdk/version/src/SeedClient.php index be7d76e3425..b69d9150d63 100644 --- a/seed/php-sdk/version/src/SeedClient.php +++ b/seed/php-sdk/version/src/SeedClient.php @@ -4,7 +4,7 @@ use Seed\User\UserClient; use GuzzleHttp\ClientInterface; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; class SeedClient { diff --git a/seed/php-sdk/version/src/User/Types/User.php b/seed/php-sdk/version/src/User/Types/User.php index af657430e5c..5c95ec2354c 100644 --- a/seed/php-sdk/version/src/User/Types/User.php +++ b/seed/php-sdk/version/src/User/Types/User.php @@ -2,8 +2,8 @@ namespace Seed\User\Types; -use Seed\Core\SerializableType; -use Seed\Core\JsonProperty; +use Seed\Core\Json\SerializableType; +use Seed\Core\Json\JsonProperty; class User extends SerializableType { diff --git a/seed/php-sdk/version/src/User/UserClient.php b/seed/php-sdk/version/src/User/UserClient.php index ed192c1f300..b93ac493fba 100644 --- a/seed/php-sdk/version/src/User/UserClient.php +++ b/seed/php-sdk/version/src/User/UserClient.php @@ -2,12 +2,12 @@ namespace Seed\User; -use Seed\Core\RawClient; +use Seed\Core\Client\RawClient; use Seed\User\Types\User; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; -use Seed\Core\JsonApiRequest; -use Seed\Core\HttpMethod; +use Seed\Core\Json\JsonApiRequest; +use Seed\Core\Client\HttpMethod; use JsonException; use Psr\Http\Client\ClientExceptionInterface; diff --git a/seed/php-sdk/version/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/version/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/version/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/EnumTest.php b/seed/php-sdk/version/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/version/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/version/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/version/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/version/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/version/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/version/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/version/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/version/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} diff --git a/seed/php-sdk/websocket/src/Core/ArrayType.php b/seed/php-sdk/websocket/src/Core/ArrayType.php deleted file mode 100644 index b2ed8bf12b2..00000000000 --- a/seed/php-sdk/websocket/src/Core/ArrayType.php +++ /dev/null @@ -1,16 +0,0 @@ - 'valueType'] for maps, or ['valueType'] for lists - */ - public function __construct(public array $type) - { - } -} diff --git a/seed/php-sdk/websocket/src/Core/BaseApiRequest.php b/seed/php-sdk/websocket/src/Core/BaseApiRequest.php deleted file mode 100644 index 2ace034ec90..00000000000 --- a/seed/php-sdk/websocket/src/Core/BaseApiRequest.php +++ /dev/null @@ -1,22 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - */ - public function __construct( - public readonly string $baseUrl, - public readonly string $path, - public readonly HttpMethod $method, - public readonly array $headers = [], - public readonly array $query = [], - ) { - } -} diff --git a/seed/php-sdk/websocket/src/Core/Client/BaseApiRequest.php b/seed/php-sdk/websocket/src/Core/Client/BaseApiRequest.php new file mode 100644 index 00000000000..5e1283e2b6f --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Client/BaseApiRequest.php @@ -0,0 +1,22 @@ + $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + */ + public function __construct( + public readonly string $baseUrl, + public readonly string $path, + public readonly HttpMethod $method, + public readonly array $headers = [], + public readonly array $query = [], + ) { + } +} diff --git a/seed/php-sdk/websocket/src/Core/Client/HttpMethod.php b/seed/php-sdk/websocket/src/Core/Client/HttpMethod.php new file mode 100644 index 00000000000..b9a3e2d0321 --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Client/HttpMethod.php @@ -0,0 +1,12 @@ + $headers + */ + private array $headers; + + /** + * @param ?array{ + * baseUrl?: string, + * client?: ClientInterface, + * headers?: array, + * } $options + */ + public function __construct( + public readonly ?array $options = null, + ) { + $this->client = $this->options['client'] ?? new Client(); + $this->headers = $this->options['headers'] ?? []; + } + + /** + * @throws ClientExceptionInterface + */ + public function sendRequest( + BaseApiRequest $request, + ): ResponseInterface { + $httpRequest = $this->buildRequest($request); + return $this->client->send($httpRequest); + } + + private function buildRequest( + BaseApiRequest $request + ): Request { + $url = $this->buildUrl($request); + $headers = $this->encodeHeaders($request); + $body = $this->encodeRequestBody($request); + return new Request( + $request->method->name, + $url, + $headers, + $body, + ); + } + + /** + * @return array + */ + private function encodeHeaders( + BaseApiRequest $request + ): array { + return match (get_class($request)) { + JsonApiRequest::class => array_merge( + ["Content-Type" => "application/json"], + $this->headers, + $request->headers + ), + default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), + }; + } + + private function encodeRequestBody( + BaseApiRequest $request + ): ?StreamInterface { + return match (get_class($request)) { + JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, + default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), + }; + } + + private function buildUrl( + BaseApiRequest $request + ): string { + $baseUrl = $request->baseUrl; + $trimmedBaseUrl = rtrim($baseUrl, '/'); + $trimmedBasePath = ltrim($request->path, '/'); + $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; + + if (!empty($request->query)) { + $url .= '?' . $this->encodeQuery($request->query); + } + + return $url; + } + + /** + * @param array $query + */ + private function encodeQuery( + array $query + ): string { + $parts = []; + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $item) { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); + } + } else { + $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); + } + } + return implode('&', $parts); + } + + private function encodeQueryValue( + mixed $value + ): string { + if (is_string($value)) { + return urlencode($value); + } + if (is_scalar($value)) { + return urlencode((string)$value); + } + if (is_null($value)) { + return 'null'; + } + // Unreachable, but included for a best effort. + return urlencode(strval(json_encode($value))); + } +} diff --git a/seed/php-sdk/websocket/src/Core/Constant.php b/seed/php-sdk/websocket/src/Core/Constant.php deleted file mode 100644 index abbac7f6649..00000000000 --- a/seed/php-sdk/websocket/src/Core/Constant.php +++ /dev/null @@ -1,12 +0,0 @@ - $headers Additional headers for the request (optional) + * @param array $query Query parameters for the request (optional) + * @param mixed|null $body The JSON request body (optional) + */ + public function __construct( + string $baseUrl, + string $path, + HttpMethod $method, + array $headers = [], + array $query = [], + public readonly mixed $body = null + ) { + parent::__construct($baseUrl, $path, $method, $headers, $query); + } +} diff --git a/seed/php-sdk/websocket/src/Core/Json/JsonDecoder.php b/seed/php-sdk/websocket/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-sdk/websocket/src/Core/Json/JsonDeserializer.php b/seed/php-sdk/websocket/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..eff3203e494 --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement SerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, SerializableType::class)) { + throw new JsonException("$type is not a subclass of SerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/websocket/src/Core/Json/JsonEncoder.php b/seed/php-sdk/websocket/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-sdk/websocket/src/Core/Json/SerializableType.php b/seed/php-sdk/websocket/src/Core/Json/SerializableType.php new file mode 100644 index 00000000000..e6c1efc4457 --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Json/SerializableType.php @@ -0,0 +1,182 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === DateType::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle DateType annotation + $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === DateType::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle ArrayType annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-sdk/websocket/src/Core/Json/Utils.php b/seed/php-sdk/websocket/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-sdk/websocket/src/Core/JsonApiRequest.php b/seed/php-sdk/websocket/src/Core/JsonApiRequest.php deleted file mode 100644 index 4c0e7dd01e8..00000000000 --- a/seed/php-sdk/websocket/src/Core/JsonApiRequest.php +++ /dev/null @@ -1,25 +0,0 @@ - $headers Additional headers for the request (optional) - * @param array $query Query parameters for the request (optional) - * @param mixed|null $body The JSON request body (optional) - */ - public function __construct( - string $baseUrl, - string $path, - HttpMethod $method, - array $headers = [], - array $query = [], - public readonly mixed $body = null - ) { - parent::__construct($baseUrl, $path, $method, $headers, $query); - } -} diff --git a/seed/php-sdk/websocket/src/Core/JsonDecoder.php b/seed/php-sdk/websocket/src/Core/JsonDecoder.php deleted file mode 100644 index c7f9629e018..00000000000 --- a/seed/php-sdk/websocket/src/Core/JsonDecoder.php +++ /dev/null @@ -1,160 +0,0 @@ - $type The type definition for deserialization. - * @return mixed[]|array The deserialized array. - * @throws JsonException If the decoded value is not an array. - */ - public static function decodeArray(string $json, array $type): array - { - $decoded = self::decode($json); - if (!is_array($decoded)) { - throw new JsonException("Unexpected non-array json value: " . $json); - } - return JsonDeserializer::deserializeArray($decoded, $type); - } - - /** - * Decodes a JSON string and deserializes it based on the provided union type definition. - * - * @param string $json The JSON string to decode. - * @param Union $union The union type definition for deserialization. - * @return mixed The deserialized value. - * @throws JsonException If the deserialization for all types in the union fails. - */ - public static function decodeUnion(string $json, Union $union): mixed - { - $decoded = self::decode($json); - return JsonDeserializer::deserializeUnion($decoded, $union); - } - /** - * Decodes a JSON string and returns a mixed. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded mixed. - * @throws JsonException If the decoded value is not an mixed. - */ - public static function decodeMixed(string $json): mixed - { - return self::decode($json); - } - - /** - * Decodes a JSON string into a PHP value. - * - * @param string $json The JSON string to decode. - * @return mixed The decoded value. - * @throws JsonException If an error occurs during JSON decoding. - */ - public static function decode(string $json): mixed - { - return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); - } -} diff --git a/seed/php-sdk/websocket/src/Core/JsonDeserializer.php b/seed/php-sdk/websocket/src/Core/JsonDeserializer.php deleted file mode 100644 index b1de7d141ac..00000000000 --- a/seed/php-sdk/websocket/src/Core/JsonDeserializer.php +++ /dev/null @@ -1,202 +0,0 @@ - $data The array to be deserialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The deserialized array. - * @throws JsonException If deserialization fails. - */ - public static function deserializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::deserializeMap($data, $type) - : self::deserializeList($data, $type); - } - - /** - * Deserializes a value based on its type definition. - * - * @param mixed $data The data to deserialize. - * @param mixed $type The type definition. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::deserializeUnion($data, $type); - } - - if (is_array($type)) { - return self::deserializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::deserializeSingleValue($data, $type); - } - - /** - * Deserializes a value based on the possible types in a union type definition. - * - * @param mixed $data The data to deserialize. - * @param Union $type The union type definition. - * @return mixed The deserialized value. - * @throws JsonException If none of the union types can successfully deserialize the value. - */ - public static function deserializeUnion(mixed $data, Union $type): mixed - { - foreach ($type->types as $unionType) { - try { - return self::deserializeValue($data, $unionType); - } catch (Exception) { - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot deserialize value of type $readableType with any of the union types: " . $type - ); - } - - /** - * Deserializes a single value based on its expected type. - * - * @param mixed $data The data to deserialize. - * @param string $type The expected type. - * @return mixed The deserialized value. - * @throws JsonException If deserialization fails. - */ - private static function deserializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if ($type === 'date' && is_string($data)) { - return self::deserializeDate($data); - } - - if ($type === 'datetime' && is_string($data)) { - return self::deserializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && is_array($data)) { - return self::deserializeObject($data, $type); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because - // floats make come through from json_decoded as integers - if ($type === 'float' && (is_numeric($data))) { - return (float) $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Deserializes an array into an object of the given type. - * - * @param array $data The data to deserialize. - * @param string $type The class name of the object to deserialize into. - * - * @return object The deserialized object. - * - * @throws JsonException If the type does not implement SerializableType. - */ - public static function deserializeObject(array $data, string $type): object - { - if (!is_subclass_of($type, SerializableType::class)) { - throw new JsonException("$type is not a subclass of SerializableType."); - } - return $type::jsonDeserialize($data); - } - - /** - * Deserializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to deserialize. - * @param array $type The type definition for the map. - * @return array The deserialized map. - * @throws JsonException If deserialization fails. - */ - private static function deserializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, (string)$keyType); - $result[$key] = self::deserializeValue($item, $valueType); - } - - return $result; - } - - /** - * Deserializes a list (indexed array) with a defined value type. - * - * @param array $data The list to deserialize. - * @param array $type The type definition for the list. - * @return array The deserialized list. - * @throws JsonException If deserialization fails. - */ - private static function deserializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/websocket/src/Core/JsonEncoder.php b/seed/php-sdk/websocket/src/Core/JsonEncoder.php deleted file mode 100644 index ba5191a8068..00000000000 --- a/seed/php-sdk/websocket/src/Core/JsonEncoder.php +++ /dev/null @@ -1,20 +0,0 @@ -format(Constant::DateFormat); - } - - /** - * Serializes a DateTime object into a string using the date-time format. - * - * @param DateTime $date The DateTime object to serialize. - * @return string The serialized date-time string. - */ - public static function serializeDateTime(DateTime $date): string - { - return $date->format(Constant::DateTimeFormat); - } - - /** - * Serializes an array based on type annotations (either a list or map). - * - * @param mixed[]|array $data The array to be serialized. - * @param mixed[]|array $type The type definition from the annotation. - * @return mixed[]|array The serialized array. - * @throws JsonException If serialization fails. - */ - public static function serializeArray(array $data, array $type): array - { - return Utils::isMapType($type) - ? self::serializeMap($data, $type) - : self::serializeList($data, $type); - } - - /** - * Serializes a value based on its type definition. - * - * @param mixed $data The value to serialize. - * @param mixed $type The type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeValue(mixed $data, mixed $type): mixed - { - if ($type instanceof Union) { - return self::serializeUnion($data, $type); - } - - if (is_array($type)) { - return self::serializeArray((array)$data, $type); - } - - if (gettype($type) != "string") { - throw new JsonException("Unexpected non-string type."); - } - - return self::serializeSingleValue($data, $type); - } - - /** - * Serializes a value for a union type definition. - * - * @param mixed $data The value to serialize. - * @param Union $unionType The union type definition. - * @return mixed The serialized value. - * @throws JsonException If serialization fails for all union types. - */ - public static function serializeUnion(mixed $data, Union $unionType): mixed - { - foreach ($unionType->types as $type) { - try { - return self::serializeValue($data, $type); - } catch (Exception) { - // Try the next type in the union - continue; - } - } - $readableType = Utils::getReadableType($data); - throw new JsonException( - "Cannot serialize value of type $readableType with any of the union types: " . $unionType - ); - } - - /** - * Serializes a single value based on its type. - * - * @param mixed $data The value to serialize. - * @param string $type The expected type. - * @return mixed The serialized value. - * @throws JsonException If serialization fails. - */ - private static function serializeSingleValue(mixed $data, string $type): mixed - { - if ($type === 'null' && $data === null) { - return null; - } - - if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { - return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); - } - - if ($type === 'mixed') { - return $data; - } - - if (class_exists($type) && $data instanceof $type) { - return self::serializeObject($data); - } - - // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. - if ($type === 'float' && is_float($data)) { - return $data; - } - - if (gettype($data) === $type) { - return $data; - } - - throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); - } - - /** - * Serializes an object to a JSON-serializable format. - * - * @param object $data The object to serialize. - * @return mixed The serialized data. - * @throws JsonException If the object does not implement JsonSerializable. - */ - public static function serializeObject(object $data): mixed - { - if (!is_subclass_of($data, JsonSerializable::class)) { - $type = get_class($data); - throw new JsonException("Class $type must implement JsonSerializable."); - } - return $data->jsonSerialize(); - } - - /** - * Serializes a map (associative array) with defined key and value types. - * - * @param array $data The associative array to serialize. - * @param array $type The type definition for the map. - * @return array The serialized map. - * @throws JsonException If serialization fails. - */ - private static function serializeMap(array $data, array $type): array - { - $keyType = array_key_first($type); - if ($keyType === null) { - throw new JsonException("Unexpected no key in ArrayType."); - } - $valueType = $type[$keyType]; - $result = []; - - foreach ($data as $key => $item) { - $key = Utils::castKey($key, $keyType); - $result[$key] = self::serializeValue($item, $valueType); - } - - return $result; - } - - /** - * Serializes a list (indexed array) where only the value type is defined. - * - * @param array $data The list to serialize. - * @param array $type The type definition for the list. - * @return array The serialized list. - * @throws JsonException If serialization fails. - */ - private static function serializeList(array $data, array $type): array - { - $valueType = $type[0]; - return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); - } -} diff --git a/seed/php-sdk/websocket/src/Core/RawClient.php b/seed/php-sdk/websocket/src/Core/RawClient.php deleted file mode 100644 index 1c0e42bf650..00000000000 --- a/seed/php-sdk/websocket/src/Core/RawClient.php +++ /dev/null @@ -1,138 +0,0 @@ - $headers - */ - private array $headers; - - /** - * @param ?array{ - * baseUrl?: string, - * client?: ClientInterface, - * headers?: array, - * } $options - */ - public function __construct( - public readonly ?array $options = null, - ) { - $this->client = $this->options['client'] ?? new Client(); - $this->headers = $this->options['headers'] ?? []; - } - - /** - * @throws ClientExceptionInterface - */ - public function sendRequest( - BaseApiRequest $request, - ): ResponseInterface { - $httpRequest = $this->buildRequest($request); - return $this->client->send($httpRequest); - } - - private function buildRequest( - BaseApiRequest $request - ): Request { - $url = $this->buildUrl($request); - $headers = $this->encodeHeaders($request); - $body = $this->encodeRequestBody($request); - return new Request( - $request->method->name, - $url, - $headers, - $body, - ); - } - - /** - * @return array - */ - private function encodeHeaders( - BaseApiRequest $request - ): array { - return match (get_class($request)) { - JsonApiRequest::class => array_merge( - ["Content-Type" => "application/json"], - $this->headers, - $request->headers - ), - default => throw new InvalidArgumentException('Unsupported request type: ' . get_class($request)), - }; - } - - private function encodeRequestBody( - BaseApiRequest $request - ): ?StreamInterface { - return match (get_class($request)) { - JsonApiRequest::class => $request->body != null ? Utils::streamFor(json_encode($request->body)) : null, - default => throw new InvalidArgumentException('Unsupported request type: '.get_class($request)), - }; - } - - private function buildUrl( - BaseApiRequest $request - ): string { - $baseUrl = $request->baseUrl; - $trimmedBaseUrl = rtrim($baseUrl, '/'); - $trimmedBasePath = ltrim($request->path, '/'); - $url = "{$trimmedBaseUrl}/{$trimmedBasePath}"; - - if (!empty($request->query)) { - $url .= '?' . $this->encodeQuery($request->query); - } - - return $url; - } - - /** - * @param array $query - */ - private function encodeQuery( - array $query - ): string { - $parts = []; - foreach ($query as $key => $value) { - if (is_array($value)) { - foreach ($value as $item) { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($item); - } - } else { - $parts[] = urlencode($key).'='.$this->encodeQueryValue($value); - } - } - return implode('&', $parts); - } - - private function encodeQueryValue( - mixed $value - ): string { - if (is_string($value)) { - return urlencode($value); - } - if (is_scalar($value)) { - return urlencode((string)$value); - } - if (is_null($value)) { - return 'null'; - } - // Unreachable, but included for a best effort. - return urlencode(strval(json_encode($value))); - } -} diff --git a/seed/php-sdk/websocket/src/Core/SerializableType.php b/seed/php-sdk/websocket/src/Core/SerializableType.php deleted file mode 100644 index 9121bdca01c..00000000000 --- a/seed/php-sdk/websocket/src/Core/SerializableType.php +++ /dev/null @@ -1,179 +0,0 @@ -jsonSerialize(); - $encoded = JsonEncoder::encode($serializedObject); - if (!$encoded) { - throw new Exception("Could not encode type"); - } - return $encoded; - } - - /** - * Serializes the object to an array. - * - * @return mixed[] Array representation of the object. - * @throws JsonException If serialization fails. - */ - public function jsonSerialize(): array - { - $result = []; - $reflectionClass = new \ReflectionClass($this); - - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property); - if ($jsonKey == null) { - continue; - } - $value = $property->getValue($this); - - // Handle DateTime properties - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr && $value instanceof DateTime) { - $dateType = $dateTypeAttr->newInstance()->type; - $value = ($dateType === DateType::TYPE_DATE) - ? JsonSerializer::serializeDate($value) - : JsonSerializer::serializeDateTime($value); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonSerializer::serializeUnion($value, $unionType); - } - - // Handle arrays with type annotations - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if ($arrayTypeAttr && is_array($value)) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonSerializer::serializeArray($value, $arrayType); - } - - // Handle object - if (is_object($value)) { - $value = JsonSerializer::serializeObject($value); - } - - if ($value !== null) { - $result[$jsonKey] = $value; - } - } - - return $result; - } - - /** - * Deserializes a JSON string into an instance of the calling class. - * - * @param string $json JSON string to deserialize. - * @return static Deserialized object. - * @throws JsonException If decoding fails or the result is not an array. - * @throws Exception If deserialization fails. - */ - public static function fromJson(string $json): static - { - $decodedJson = JsonDecoder::decode($json); - if (!is_array($decodedJson)) { - throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); - } - return self::jsonDeserialize($decodedJson); - } - - /** - * Deserializes an array into an instance of the calling class. - * - * @param array $data Array data to deserialize. - * @return static Deserialized object. - * @throws JsonException If deserialization fails. - */ - public static function jsonDeserialize(array $data): static - { - $reflectionClass = new \ReflectionClass(static::class); - $constructor = $reflectionClass->getConstructor(); - - if ($constructor === null) { - throw new JsonException("No constructor found."); - } - - $args = []; - foreach ($reflectionClass->getProperties() as $property) { - $jsonKey = self::getJsonKey($property) ?? $property->getName(); - - if (array_key_exists($jsonKey, $data)) { - $value = $data[$jsonKey]; - - // Handle DateType annotation - $dateTypeAttr = $property->getAttributes(DateType::class)[0] ?? null; - if ($dateTypeAttr) { - $dateType = $dateTypeAttr->newInstance()->type; - if (!is_string($value)) { - throw new JsonException("Unexpected non-string type for date."); - } - $value = ($dateType === DateType::TYPE_DATE) - ? JsonDeserializer::deserializeDate($value) - : JsonDeserializer::deserializeDateTime($value); - } - - // Handle ArrayType annotation - $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; - if (is_array($value) && $arrayTypeAttr) { - $arrayType = $arrayTypeAttr->newInstance()->type; - $value = JsonDeserializer::deserializeArray($value, $arrayType); - } - - // Handle Union annotations - $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; - if ($unionTypeAttr) { - $unionType = $unionTypeAttr->newInstance(); - $value = JsonDeserializer::deserializeUnion($value, $unionType); - } - - // Handle object - $type = $property->getType(); - if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { - $value = JsonDeserializer::deserializeObject($value, $type->getName()); - } - - $args[$property->getName()] = $value; - } else { - $defaultValue = $property->getDefaultValue() ?? null; - $args[$property->getName()] = $defaultValue; - } - } - // @phpstan-ignore-next-line - return new static($args); - } - - /** - * Retrieves the JSON key associated with a property. - * - * @param ReflectionProperty $property The reflection property. - * @return ?string The JSON key, or null if not available. - */ - private static function getJsonKey(ReflectionProperty $property): ?string - { - $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; - return $jsonPropertyAttr?->newInstance()?->name; - } -} diff --git a/seed/php-sdk/websocket/src/Core/Types/ArrayType.php b/seed/php-sdk/websocket/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-sdk/websocket/src/Core/Types/Constant.php b/seed/php-sdk/websocket/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-sdk/websocket/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-sdk/websocket/src/Core/Union.php b/seed/php-sdk/websocket/src/Core/Union.php deleted file mode 100644 index 1e9fe801ee7..00000000000 --- a/seed/php-sdk/websocket/src/Core/Union.php +++ /dev/null @@ -1,62 +0,0 @@ -> The types allowed for this property, which can be strings, arrays, or nested Union types. - */ - public array $types; - - /** - * Constructor for the Union attribute. - * - * @param string|Union|array ...$types The list of types that the property can accept. - * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. - * - * Example: - * ```php - * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] - * ``` - */ - public function __construct(string|Union|array ...$types) - { - $this->types = $types; - } - - /** - * Converts the Union type to a string representation. - * - * @return string A string representation of the union types. - */ - public function __toString(): string - { - return implode(' | ', array_map(function ($type) { - if (is_string($type)) { - return $type; - } elseif ($type instanceof Union) { - return (string) $type; // Recursively handle nested unions - } elseif (is_array($type)) { - return 'array'; // Handle arrays - } - }, $this->types)); - } -} diff --git a/seed/php-sdk/websocket/src/Core/Utils.php b/seed/php-sdk/websocket/src/Core/Utils.php deleted file mode 100644 index 74416068d02..00000000000 --- a/seed/php-sdk/websocket/src/Core/Utils.php +++ /dev/null @@ -1,61 +0,0 @@ - $type The type definition from the annotation. - * @return bool True if the type is a map, false if it's a list. - */ - public static function isMapType(array $type): bool - { - return count($type) === 1 && !array_is_list($type); - } - - /** - * Casts the key to the appropriate type based on the key type. - * - * @param mixed $key The key to be cast. - * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). - * @return mixed The casted key. - * @throws JsonException - */ - public static function castKey(mixed $key, string $keyType): mixed - { - if (!is_scalar($key)) { - throw new JsonException("Key must be a scalar type."); - } - return match ($keyType) { - 'integer' => (int)$key, - 'float' => (float)$key, - 'string' => (string)$key, - default => $key, - }; - } - - /** - * Returns a human-readable representation of the input's type. - * - * @param mixed $input The input value to determine the type of. - * @return string A readable description of the input type. - */ - public static function getReadableType(mixed $input): string - { - if (is_object($input)) { - return get_class($input); - } elseif (is_array($input)) { - return 'array(' . count($input) . ' items)'; - } elseif (is_null($input)) { - return 'null'; - } else { - return gettype($input); - } - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Client/RawClientTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Client/RawClientTest.php new file mode 100644 index 00000000000..a1052cff3a5 --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Client/RawClientTest.php @@ -0,0 +1,101 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $client = new Client(['handler' => $handlerStack]); + $this->rawClient = new RawClient(['client' => $client]); + } + + public function testHeaders(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + ['X-Custom-Header' => 'TestValue'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); + } + + public function testQueryParameters(): void + { + $this->mockHandler->append(new Response(200)); + + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::GET, + [], + ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals( + 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', + (string)$lastRequest->getUri() + ); + } + + public function testJsonBody(): void + { + $this->mockHandler->append(new Response(200)); + + $body = ['key' => 'value']; + $request = new JsonApiRequest( + $this->baseUrl, + '/test', + HttpMethod::POST, + [], + [], + $body + ); + + $this->sendRequest($request); + + $lastRequest = $this->mockHandler->getLastRequest(); + assert($lastRequest instanceof RequestInterface); + $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); + $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); + } + + private function sendRequest(BaseApiRequest $request): void + { + try { + $this->rawClient->sendRequest($request); + } catch (\Throwable $e) { + $this->fail('An exception was thrown: ' . $e->getMessage()); + } + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/DateArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/DateArrayTypeTest.php deleted file mode 100644 index 8d93afc9e44..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/DateArrayTypeTest.php +++ /dev/null @@ -1,55 +0,0 @@ -dates = $values['dates']; - } -} - -class DateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInArrays(): void - { - $data = [ - 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = DateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); - $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/EmptyArraysTest.php b/seed/php-sdk/websocket/tests/Seed/Core/EmptyArraysTest.php deleted file mode 100644 index b44f3d093e6..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/EmptyArraysTest.php +++ /dev/null @@ -1,73 +0,0 @@ - $emptyMapArray - */ - #[JsonProperty('empty_map_array')] - #[ArrayType(['integer' => new Union('string', 'null')])] - public array $emptyMapArray; - - /** - * @var array $emptyDatesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('empty_dates_array')] - public array $emptyDatesArray; - - /** - * @param array{ - * emptyStringArray: string[], - * emptyMapArray: array, - * emptyDatesArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->emptyStringArray = $values['emptyStringArray']; - $this->emptyMapArray = $values['emptyMapArray']; - $this->emptyDatesArray = $values['emptyDatesArray']; - } -} - -class EmptyArraysTest extends TestCase -{ - public function testEmptyArrays(): void - { - $data = [ - 'empty_string_array' => [], - 'empty_map_array' => [], - 'empty_dates_array' => [] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = EmptyArraysType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); - - // Check that arrays are empty - $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); - $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); - $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/EnumTest.php b/seed/php-sdk/websocket/tests/Seed/Core/EnumTest.php deleted file mode 100644 index ef5b8484dfd..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/EnumTest.php +++ /dev/null @@ -1,76 +0,0 @@ -value; - } -} - -class ShapeType extends SerializableType -{ - /** - * @var Shape $shape - */ - #[JsonProperty('shape')] - public Shape $shape; - - /** - * @var Shape[] $shapes - */ - #[ArrayType([Shape::class])] - #[JsonProperty('shapes')] - public array $shapes; - - /** - * @param Shape $shape - * @param Shape[] $shapes - */ - public function __construct( - Shape $shape, - array $shapes, - ) { - $this->shape = $shape; - $this->shapes = $shapes; - } -} - -class EnumTest extends TestCase -{ - public function testShapeEnumSerialization(): void - { - $object = new ShapeType( - Shape::Circle, - [Shape::Square, Shape::Circle, Shape::Triangle] - ); - - $expectedJson = json_encode([ - 'shape' => 'CIRCLE', - 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] - ], JSON_THROW_ON_ERROR); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString( - $expectedJson, - $serializedJson, - 'Serialized JSON does not match expected JSON for shape and shapes properties.' - ); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/InvalidTypesTest.php b/seed/php-sdk/websocket/tests/Seed/Core/InvalidTypesTest.php deleted file mode 100644 index 67bfd235b2f..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/InvalidTypesTest.php +++ /dev/null @@ -1,45 +0,0 @@ -integerProperty = $values['integerProperty']; - } -} - -class InvalidTypesTest extends TestCase -{ - public function testInvalidTypesThrowExceptions(): void - { - // Create test data with invalid type for integer_property (string instead of int) - $data = [ - 'integer_property' => 'not_an_integer' - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $this->expectException(\TypeError::class); - - // Attempt to deserialize invalid data - InvalidType::fromJson($json); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/DateArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/DateArrayTypeTest.php new file mode 100644 index 00000000000..8d78ee2af9f --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/DateArrayTypeTest.php @@ -0,0 +1,55 @@ +dates = $values['dates']; + } +} + +class DateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInArrays(): void + { + $data = [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = DateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/EmptyArraysTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/EmptyArraysTest.php new file mode 100644 index 00000000000..2825696625e --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/EmptyArraysTest.php @@ -0,0 +1,73 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArraysTest extends TestCase +{ + public function testEmptyArrays(): void + { + $data = [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = EmptyArraysType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + + // Check that arrays are empty + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/EnumTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..145bcc93cb3 --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends SerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testShapeEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $serializedJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/InvalidTypesTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/InvalidTypesTest.php new file mode 100644 index 00000000000..cbb8ddbb476 --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/InvalidTypesTest.php @@ -0,0 +1,45 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTypesTest extends TestCase +{ + public function testInvalidTypesThrowExceptions(): void + { + // Create test data with invalid type for integer_property (string instead of int) + $data = [ + 'integer_property' => 'not_an_integer' + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $this->expectException(\TypeError::class); + + // Attempt to deserialize invalid data + InvalidType::fromJson($json); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/MixedDateArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/MixedDateArrayTypeTest.php new file mode 100644 index 00000000000..74847a4260f --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/MixedDateArrayTypeTest.php @@ -0,0 +1,60 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class MixedDateArrayTypeTest extends TestCase +{ + public function testDateTimeTypesInUnionArrays(): void + { + $data = [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = MixedDateArrayType::fromJson($json); + + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php new file mode 100644 index 00000000000..9da189e8b80 --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/NestedUnionArrayTypeTest.php @@ -0,0 +1,99 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArrayType extends SerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTypeTest extends TestCase +{ + public function testNestedUnionTypesInArrays(): void + { + $data = [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NestedUnionArrayType::fromJson($json); + + // Level 1 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + + // ensure dates are set with the default time + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + + // Level 2 + $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/NullPropertyTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/NullPropertyTypeTest.php new file mode 100644 index 00000000000..3bc124778bf --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/NullPropertyTypeTest.php @@ -0,0 +1,50 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTypeTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); + + $serializedObject = $object->jsonSerialize(); + + $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); + + $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/NullableArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/NullableArrayTypeTest.php new file mode 100644 index 00000000000..c965969a278 --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/NullableArrayTypeTest.php @@ -0,0 +1,50 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTypeTest extends TestCase +{ + public function testNullableTypesInArrays(): void + { + $data = [ + 'nullable_string_array' => ['one', null, 'three'] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = NullableArrayType::fromJson($json); + + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/ScalarTypesTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/ScalarTypesTest.php new file mode 100644 index 00000000000..dc00767071d --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/ScalarTypesTest.php @@ -0,0 +1,121 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTypesTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + // Create test data + $data = [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = ScalarTypesTestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + + // Check scalar properties + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + + // Check int_float_array + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/TestTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/TestTypeTest.php new file mode 100644 index 00000000000..08a1ade8a25 --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/TestTypeTest.php @@ -0,0 +1,201 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class TestType extends SerializableType +{ + /** + * @var TestNestedType1 nestedType + */ + #[JsonProperty('nested_type')] + public TestNestedType1 $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[DateType(DateType::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[DateType(DateType::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: TestNestedType1, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class TestTypeTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in TestType + */ + public function testSerializationAndDeserialization(): void + { + // Create test data + $data = [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // 'nullable_property' is omitted to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TestType::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + // @phpstan-ignore-next-line + $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/UnionArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/UnionArrayTypeTest.php new file mode 100644 index 00000000000..4b5eda4247d --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/UnionArrayTypeTest.php @@ -0,0 +1,56 @@ + $mixedArray + */ + #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] + #[JsonProperty('mixed_array')] + public array $mixedArray; + + /** + * @param array{ + * mixedArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedArray = $values['mixedArray']; + } +} + +class UnionArrayTypeTest extends TestCase +{ + public function testUnionTypesInArrays(): void + { + $data = [ + 'mixed_array' => [ + 1 => 'one', + 2 => 2, + 3 => null + ] + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = UnionArrayType::fromJson($json); + + $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); + $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); + $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/Json/UnionPropertyTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/Json/UnionPropertyTypeTest.php new file mode 100644 index 00000000000..9eb4da26b74 --- /dev/null +++ b/seed/php-sdk/websocket/tests/Seed/Core/Json/UnionPropertyTypeTest.php @@ -0,0 +1,116 @@ + 'integer'], UnionPropertyType::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionPropertyType + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $data = [ + 'complexUnion' => [1 => 100, 2 => 200] // Map + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for map + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + // Nested instance of UnionPropertyType + $nestedData = [ + 'complexUnion' => 'Nested String' + ]; + + $data = [ + 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for UnionPropertyType instance + $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $data = []; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for null + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $data = [ + 'complexUnion' => 42 // Integer + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for integer + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $data = [ + 'complexUnion' => 'Some String' // String + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + $object = UnionPropertyType::fromJson($json); + + // Test for string + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $serializedJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/MixedDateArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/MixedDateArrayTypeTest.php deleted file mode 100644 index 3bf18aec25b..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/MixedDateArrayTypeTest.php +++ /dev/null @@ -1,60 +0,0 @@ - $mixedDates - */ - #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] - #[JsonProperty('mixed_dates')] - public array $mixedDates; - - /** - * @param array{ - * mixedDates: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedDates = $values['mixedDates']; - } -} - -class MixedDateArrayTypeTest extends TestCase -{ - public function testDateTimeTypesInUnionArrays(): void - { - $data = [ - 'mixed_dates' => [ - 1 => '2023-01-01T12:00:00+00:00', - 2 => null, - 3 => 'Some String' - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = MixedDateArrayType::fromJson($json); - - $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); - - $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); - - $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_dates.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/NestedUnionArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/NestedUnionArrayTypeTest.php deleted file mode 100644 index 4667ecafcb9..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/NestedUnionArrayTypeTest.php +++ /dev/null @@ -1,99 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class NestedUnionArrayType extends SerializableType -{ - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union(TestNestedType::class, 'null', 'date')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @param array{ - * nestedArray: array>, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedArray = $values['nestedArray']; - } -} - -class NestedUnionArrayTypeTest extends TestCase -{ - public function testNestedUnionTypesInArrays(): void - { - $data = [ - 'nested_array' => [ - 1 => [ - 1 => ['nested_property' => 'Nested One'], - 2 => null, - 4 => '2023-01-02' - ], - 2 => [ - 5 => ['nested_property' => 'Nested Two'], - 7 => '2023-02-02' - ] - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NestedUnionArrayType::fromJson($json); - - // Level 1 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of TestNestedType.'); - $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); - - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - - // ensure dates are set with the default time - $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); - - // Level 2 - $this->assertInstanceOf(TestNestedType::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of TestNestedType.'); - $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); - - $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); - $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nested_array.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/NullPropertyTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/NullPropertyTypeTest.php deleted file mode 100644 index 134296f56e3..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/NullPropertyTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ -nonNullProperty = $values['nonNullProperty']; - $this->nullProperty = $values['nullProperty'] ?? null; - } -} - -class NullPropertyTypeTest extends TestCase -{ - public function testNullPropertiesAreOmitted(): void - { - $object = new NullPropertyType(["nonNullProperty" => "Test String", "nullProperty" => null]); - - $serializedObject = $object->jsonSerialize(); - - $this->assertArrayHasKey('non_null_property', $serializedObject, 'non_null_property should be present in the serialized JSON.'); - $this->assertArrayNotHasKey('null_property', $serializedObject, 'null_property should be omitted from the serialized JSON.'); - - $this->assertEquals('Test String', $serializedObject['non_null_property'], 'non_null_property should have the correct value.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/NullableArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/NullableArrayTypeTest.php deleted file mode 100644 index bf6345e5c6f..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/NullableArrayTypeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - $nullableStringArray - */ - #[ArrayType([new Union('string', 'null')])] - #[JsonProperty('nullable_string_array')] - public array $nullableStringArray; - - /** - * @param array{ - * nullableStringArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nullableStringArray = $values['nullableStringArray']; - } -} - -class NullableArrayTypeTest extends TestCase -{ - public function testNullableTypesInArrays(): void - { - $data = [ - 'nullable_string_array' => ['one', null, 'three'] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = NullableArrayType::fromJson($json); - - $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/RawClientTest.php b/seed/php-sdk/websocket/tests/Seed/Core/RawClientTest.php deleted file mode 100644 index e01ae63b41a..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/RawClientTest.php +++ /dev/null @@ -1,101 +0,0 @@ -mockHandler = new MockHandler(); - $handlerStack = HandlerStack::create($this->mockHandler); - $client = new Client(['handler' => $handlerStack]); - $this->rawClient = new RawClient(['client' => $client]); - } - - public function testHeaders(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - ['X-Custom-Header' => 'TestValue'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals('TestValue', $lastRequest->getHeaderLine('X-Custom-Header')); - } - - public function testQueryParameters(): void - { - $this->mockHandler->append(new Response(200)); - - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::GET, - [], - ['param1' => 'value1', 'param2' => ['a', 'b'], 'param3' => 'true'] - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals( - 'https://api.example.com/test?param1=value1¶m2=a¶m2=b¶m3=true', - (string)$lastRequest->getUri() - ); - } - - public function testJsonBody(): void - { - $this->mockHandler->append(new Response(200)); - - $body = ['key' => 'value']; - $request = new JsonApiRequest( - $this->baseUrl, - '/test', - HttpMethod::POST, - [], - [], - $body - ); - - $this->sendRequest($request); - - $lastRequest = $this->mockHandler->getLastRequest(); - assert($lastRequest instanceof RequestInterface); - $this->assertEquals('application/json', $lastRequest->getHeaderLine('Content-Type')); - $this->assertEquals(json_encode($body), (string)$lastRequest->getBody()); - } - - private function sendRequest(BaseApiRequest $request): void - { - try { - $this->rawClient->sendRequest($request); - } catch (\Throwable $e) { - $this->fail('An exception was thrown: ' . $e->getMessage()); - } - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/ScalarTypesTest.php b/seed/php-sdk/websocket/tests/Seed/Core/ScalarTypesTest.php deleted file mode 100644 index 899e949836c..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/ScalarTypesTest.php +++ /dev/null @@ -1,121 +0,0 @@ - $intFloatArray - */ - #[ArrayType([new Union('integer', 'float')])] - #[JsonProperty('int_float_array')] - public array $intFloatArray; - - /** - * @var array $floatArray - */ - #[ArrayType(['float'])] - #[JsonProperty('float_array')] - public array $floatArray; - - /** - * @var bool|null $nullableBooleanProperty - */ - #[JsonProperty('nullable_boolean_property')] - public ?bool $nullableBooleanProperty; - - /** - * @param array{ - * integerProperty: int, - * floatProperty: float, - * otherFloatProperty: float, - * booleanProperty: bool, - * stringProperty: string, - * intFloatArray: array, - * floatArray: array, - * nullableBooleanProperty?: bool|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->integerProperty = $values['integerProperty']; - $this->floatProperty = $values['floatProperty']; - $this->otherFloatProperty = $values['otherFloatProperty']; - $this->booleanProperty = $values['booleanProperty']; - $this->stringProperty = $values['stringProperty']; - $this->intFloatArray = $values['intFloatArray']; - $this->floatArray = $values['floatArray']; - $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; - } -} - -class ScalarTypesTest extends TestCase -{ - public function testAllScalarTypesIncludingFloat(): void - { - // Create test data - $data = [ - 'integer_property' => 42, - 'float_property' => 3.14159, - 'other_float_property' => 3, - 'boolean_property' => true, - 'string_property' => 'Hello, World!', - 'int_float_array' => [1, 2.5, 3, 4.75], - 'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = ScalarTypesTestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); - - // Check scalar properties - $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); - $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); - $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); - $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); - $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); - - // Check int_float_array - $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/TestTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/TestTypeTest.php deleted file mode 100644 index 8e7ca1b825c..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/TestTypeTest.php +++ /dev/null @@ -1,201 +0,0 @@ -nestedProperty = $values['nestedProperty']; - } -} - -class TestType extends SerializableType -{ - /** - * @var TestNestedType1 nestedType - */ - #[JsonProperty('nested_type')] - public TestNestedType1 $nestedType; /** - - * @var string $simpleProperty - */ - #[JsonProperty('simple_property')] - public string $simpleProperty; - - /** - * @var DateTime $dateProperty - */ - #[DateType(DateType::TYPE_DATE)] - #[JsonProperty('date_property')] - public DateTime $dateProperty; - - /** - * @var DateTime $datetimeProperty - */ - #[DateType(DateType::TYPE_DATETIME)] - #[JsonProperty('datetime_property')] - public DateTime $datetimeProperty; - - /** - * @var array $stringArray - */ - #[ArrayType(['string'])] - #[JsonProperty('string_array')] - public array $stringArray; - - /** - * @var array $mapProperty - */ - #[ArrayType(['string' => 'integer'])] - #[JsonProperty('map_property')] - public array $mapProperty; - - /** - * @var array $objectArray - */ - #[ArrayType(['integer' => new Union(TestNestedType1::class, 'null')])] - #[JsonProperty('object_array')] - public array $objectArray; - - /** - * @var array> $nestedArray - */ - #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] - #[JsonProperty('nested_array')] - public array $nestedArray; - - /** - * @var array $datesArray - */ - #[ArrayType([new Union('date', 'null')])] - #[JsonProperty('dates_array')] - public array $datesArray; - - /** - * @var string|null $nullableProperty - */ - #[JsonProperty('nullable_property')] - public ?string $nullableProperty; - - /** - * @param array{ - * nestedType: TestNestedType1, - * simpleProperty: string, - * dateProperty: DateTime, - * datetimeProperty: DateTime, - * stringArray: array, - * mapProperty: array, - * objectArray: array, - * nestedArray: array>, - * datesArray: array, - * nullableProperty?: string|null, - * } $values - */ - public function __construct( - array $values, - ) { - $this->nestedType = $values['nestedType']; - $this->simpleProperty = $values['simpleProperty']; - $this->dateProperty = $values['dateProperty']; - $this->datetimeProperty = $values['datetimeProperty']; - $this->stringArray = $values['stringArray']; - $this->mapProperty = $values['mapProperty']; - $this->objectArray = $values['objectArray']; - $this->nestedArray = $values['nestedArray']; - $this->datesArray = $values['datesArray']; - $this->nullableProperty = $values['nullableProperty'] ?? null; - } -} - -class TestTypeTest extends TestCase -{ - /** - * Test serialization and deserialization of all types in TestType - */ - public function testSerializationAndDeserialization(): void - { - // Create test data - $data = [ - 'nested_type' => ['nested_property' => '1995-07-20'], - 'simple_property' => 'Test String', - // 'nullable_property' is omitted to test null serialization - 'date_property' => '2023-01-01', - 'datetime_property' => '2023-01-01T12:34:56+00:00', - 'string_array' => ['one', 'two', 'three'], - 'map_property' => ['key1' => 1, 'key2' => 2], - 'object_array' => [ - 1 => ['nested_property' => '2021-07-20'], - 2 => null, // Testing nullable objects in array - ], - 'nested_array' => [ - 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array - 2 => [3 => 'value3', 4 => 'value4'] - ], - 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = TestType::fromJson($json); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'The serialized JSON does not match the original JSON.'); - - // Check that nullable property is null and not included in JSON - $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); - // @phpstan-ignore-next-line - $this->assertFalse(array_key_exists('nullable_property', json_decode($serializedJson, true)), 'Nullable property should be omitted from JSON.'); - - // Check date properties - $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); - - $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); - $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); - - // Check scalar arrays - $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); - $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); - - // Check object array with nullable elements - $this->assertInstanceOf(TestNestedType1::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); - $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); - $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); - - // Check nested array with nullable strings - $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); - $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); - $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); - $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); - - // Check dates array with nullable DateTime objects - $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); - $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); - $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); - $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); - $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/UnionArrayTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/UnionArrayTypeTest.php deleted file mode 100644 index 8d0998f4b7e..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/UnionArrayTypeTest.php +++ /dev/null @@ -1,56 +0,0 @@ - $mixedArray - */ - #[ArrayType(['integer' => new Union('string', 'integer', 'null')])] - #[JsonProperty('mixed_array')] - public array $mixedArray; - - /** - * @param array{ - * mixedArray: array, - * } $values - */ - public function __construct( - array $values, - ) { - $this->mixedArray = $values['mixedArray']; - } -} - -class UnionArrayTypeTest extends TestCase -{ - public function testUnionTypesInArrays(): void - { - $data = [ - 'mixed_array' => [ - 1 => 'one', - 2 => 2, - 3 => null - ] - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - - $object = UnionArrayType::fromJson($json); - - $this->assertEquals('one', $object->mixedArray[1], 'mixed_array[1] should be "one".'); - $this->assertEquals(2, $object->mixedArray[2], 'mixed_array[2] should be 2.'); - $this->assertNull($object->mixedArray[3], 'mixed_array[3] should be null.'); - - $serializedJson = $object->toJson(); - - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for mixed_array.'); - } -} diff --git a/seed/php-sdk/websocket/tests/Seed/Core/UnionPropertyTypeTest.php b/seed/php-sdk/websocket/tests/Seed/Core/UnionPropertyTypeTest.php deleted file mode 100644 index e278eb42883..00000000000 --- a/seed/php-sdk/websocket/tests/Seed/Core/UnionPropertyTypeTest.php +++ /dev/null @@ -1,116 +0,0 @@ - 'integer'], UnionPropertyType::class)] - #[JsonProperty('complexUnion')] - public mixed $complexUnion; - - /** - * @param array{ - * complexUnion: string|int|null|array|UnionPropertyType - * } $values - */ - public function __construct( - array $values, - ) { - $this->complexUnion = $values['complexUnion']; - } -} - -class UnionPropertyTest extends TestCase -{ - public function testWithMapOfIntToInt(): void - { - $data = [ - 'complexUnion' => [1 => 100, 2 => 200] // Map - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for map - $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); - $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNestedUnionPropertyType(): void - { - // Nested instance of UnionPropertyType - $nestedData = [ - 'complexUnion' => 'Nested String' - ]; - - $data = [ - 'complexUnion' => new UnionPropertyType($nestedData) // UnionPropertyType instance - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for UnionPropertyType instance - $this->assertInstanceOf(UnionPropertyType::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); - $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithNull(): void - { - $data = []; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for null - $this->assertNull($object->complexUnion, 'complexUnion should be null.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithInteger(): void - { - $data = [ - 'complexUnion' => 42 // Integer - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for integer - $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); - $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } - - public function testWithString(): void - { - $data = [ - 'complexUnion' => 'Some String' // String - ]; - - $json = json_encode($data, JSON_THROW_ON_ERROR); - $object = UnionPropertyType::fromJson($json); - - // Test for string - $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); - $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); - - $serializedJson = $object->toJson(); - $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match the original JSON.'); - } -} From 06876e3c39f683134428e10039909b6eb0ba36bb Mon Sep 17 00:00:00 2001 From: Catherine Deskur <46695336+chdeskur@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:23:01 -0400 Subject: [PATCH 05/10] feat(docs): preview sdk locally (#4793) --- fern/docs.yml | 2 + .../preview-your-sdk-locally.mdx | 73 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 fern/pages/sdks/getting-started/preview-your-sdk-locally.mdx diff --git a/fern/docs.yml b/fern/docs.yml index 774cef18c38..dcb8464aba3 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -209,6 +209,8 @@ navigation: contents: - page: Generate your First SDK path: ./pages/sdks/getting-started/generate-your-first-sdk.mdx + - page: Preview your SDK Locally + path: ./pages/sdks/getting-started/preview-your-sdk-locally.mdx - page: Publish a Public-Facing SDK path: ./pages/sdks/publish-sdk/publish-your-sdk.mdx hidden: true diff --git a/fern/pages/sdks/getting-started/preview-your-sdk-locally.mdx b/fern/pages/sdks/getting-started/preview-your-sdk-locally.mdx new file mode 100644 index 00000000000..622e5865daf --- /dev/null +++ b/fern/pages/sdks/getting-started/preview-your-sdk-locally.mdx @@ -0,0 +1,73 @@ +--- +title: Preview your SDK +subtitle: Use Fern's CLI tool to preview your SDK locally. +--- + +[Once you configure your SDK](/learn/sdks/getting-started/generate-your-first-sdk), you can preview the generated SDK code using Fern's CLI. + +Simply append the `--preview` flag to the command used to generate the SDK and you will see the generated code populated in a `.preview` folder within your `fern` folder. + + +[If you have added custom code to your SDK](/learn/sdks/features/augment-with-custom-code), `--preview` will preserve those changes. + + +Here's an example of how you can preview your SDK: + + +### Generator configuration +```yaml generators.yml +api: + path: ./path/to/openapi.yml +groups: + python-sdk: + generators: + - name: fernapi/fern-python-sdk + version: 3.0.0 + output: + location: pypi + package-name: imdb + token: ${PYPI_TOKEN} + github: + repository: imdb/imdb-python + config: + client_class_name: imdb +``` + +### Invoke the Fern CLI + +```shell +fern generate --group python-sdk --preview +``` + +### Preview your SDK + +The resulting folder structure will look like this: + + + +```shell {3-5} +fern/ + ├─ fern.config.json + ├─ .preview/ + └─ fern-python-sdk/ + └─ ... + ├─ generators.yml + └─ openapi/ + └─ openapi.yml +``` + + +```shell {3-5} +fern/ + ├─ fern.config.json + ├─ .preview/ + └─ fern-python-sdk/ + └─ ... + ├─ generators.yml + └─ definition/ + ├─ api.yml + └─ imdb.yml +``` + + + \ No newline at end of file From 20f3c3bcecea7dd3af4584407aafdeee18815ae0 Mon Sep 17 00:00:00 2001 From: Dan Burke Date: Wed, 2 Oct 2024 14:25:16 -0400 Subject: [PATCH 06/10] feat(php): inheritance (#4792) --- generators/php/codegen/src/AsIs.ts | 1 + .../src/asIs/Json/TraitTest.Template.php | 61 +++++++++++ generators/php/codegen/src/ast/Class.ts | 39 +++++-- generators/php/codegen/src/ast/DataClass.ts | 9 +- generators/php/codegen/src/ast/Field.ts | 6 +- generators/php/codegen/src/ast/Trait.ts | 101 ++++++++++++++++++ generators/php/codegen/src/ast/index.ts | 1 + generators/php/codegen/src/constants.ts | 1 + .../context/AbstractPhpGeneratorContext.ts | 39 ++++++- .../php/codegen/src/context/PhpTypeMapper.ts | 7 ++ generators/php/codegen/src/php.ts | 6 ++ generators/php/codegen/src/project/PhpFile.ts | 6 +- generators/php/model/src/ModelGeneratorCli.ts | 2 + generators/php/model/src/generateTraits.ts | 28 +++++ generators/php/model/src/index.ts | 1 + .../php/model/src/object/ObjectGenerator.ts | 40 ++++--- .../php/model/src/trait/TraitGenerator.ts | 58 ++++++++++ generators/php/sdk/src/SdkGeneratorCli.ts | 3 +- .../WrappedEndpointRequestGenerator.ts | 40 +++++-- generators/php/sdk/versions.yml | 5 + seed/php-model/alias-extends/src/Child.php | 5 + .../alias-extends/src/Traits/AliasType.php | 14 +++ .../alias-extends/src/Traits/Parent_.php | 14 +++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../alias/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../bytes/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../circular-references-advanced/src/A/A.php | 14 +++ .../src/Traits/RootType.php | 14 +++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../php-model/circular-references/src/A/A.php | 14 +++ .../src/Traits/RootType.php | 14 +++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../enum/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../examples/src/Types/ExtendedMovie.php | 23 ++++ .../examples/src/Types/Traits/Movie.php | 69 ++++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ seed/php-model/extends/src/ExampleType.php | 5 + seed/php-model/extends/src/Json.php | 5 + seed/php-model/extends/src/NestedType.php | 7 ++ seed/php-model/extends/src/Traits/Docs.php | 14 +++ .../extends/src/Traits/ExampleType.php | 16 +++ seed/php-model/extends/src/Traits/Json.php | 16 +++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../imdb/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../object/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../ListUsersExtendedOptionalListResponse.php | 7 ++ .../src/Users/ListUsersExtendedResponse.php | 7 ++ .../src/Users/Traits/UserOptionalListPage.php | 21 ++++ .../pagination/src/Users/Traits/UserPage.php | 21 ++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../src/Service/Response.php | 9 ++ .../src/Service/Traits/WithDocs.php | 14 +++ .../src/Traits/WithMetadata.php | 15 +++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ seed/php-model/simple-fhir/src/Account.php | 9 ++ seed/php-model/simple-fhir/src/Patient.php | 9 ++ .../simple-fhir/src/Practitioner.php | 9 ++ seed/php-model/simple-fhir/src/Script.php | 9 ++ .../simple-fhir/src/Traits/BaseResource.php | 33 ++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../php-model/trace/src/Playlist/Playlist.php | 7 ++ .../Playlist/Traits/PlaylistCreateRequest.php | 21 ++++ .../trace/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../unions/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../src/Requests/InlinedChildRequest.php | 5 + .../alias-extends/src/Traits/AliasType.php | 14 +++ .../alias-extends/src/Traits/Parent_.php | 14 +++ .../php-sdk/alias-extends/src/Types/Child.php | 5 + .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../alias/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../bytes/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../src/A/Types/A.php | 14 +++ .../src/Traits/RootType.php | 14 +++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../circular-references/src/A/Types/A.php | 14 +++ .../src/Traits/RootType.php | 14 +++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../enum/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../examples/src/Types/Traits/Movie.php | 69 ++++++++++++ .../src/Types/Types/ExtendedMovie.php | 23 ++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ seed/php-sdk/extends/src/Requests/Inlined.php | 7 ++ seed/php-sdk/extends/src/Traits/Docs.php | 14 +++ .../extends/src/Traits/ExampleType.php | 16 +++ seed/php-sdk/extends/src/Traits/Json.php | 16 +++ .../php-sdk/extends/src/Types/ExampleType.php | 5 + seed/php-sdk/extends/src/Types/Json.php | 5 + seed/php-sdk/extends/src/Types/NestedType.php | 7 ++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../imdb/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../object/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../src/Users/Traits/UserOptionalListPage.php | 21 ++++ .../pagination/src/Users/Traits/UserPage.php | 21 ++++ .../ListUsersExtendedOptionalListResponse.php | 7 ++ .../Users/Types/ListUsersExtendedResponse.php | 7 ++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../src/Service/Traits/WithDocs.php | 14 +++ .../src/Service/Types/Response.php | 9 ++ .../src/Traits/WithMetadata.php | 15 +++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../simple-fhir/src/Traits/BaseResource.php | 33 ++++++ .../php-sdk/simple-fhir/src/Types/Account.php | 9 ++ .../php-sdk/simple-fhir/src/Types/Patient.php | 9 ++ .../simple-fhir/src/Types/Practitioner.php | 9 ++ seed/php-sdk/simple-fhir/src/Types/Script.php | 9 ++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../Playlist/Traits/PlaylistCreateRequest.php | 21 ++++ .../trace/src/Playlist/Types/Playlist.php | 7 ++ .../trace/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../unions/tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ .../tests/Seed/Core/Json/TraitTest.php | 61 +++++++++++ 204 files changed, 8857 insertions(+), 43 deletions(-) create mode 100644 generators/php/codegen/src/asIs/Json/TraitTest.Template.php create mode 100644 generators/php/codegen/src/ast/Trait.ts create mode 100644 generators/php/codegen/src/constants.ts create mode 100644 generators/php/model/src/generateTraits.ts create mode 100644 generators/php/model/src/trait/TraitGenerator.ts create mode 100644 seed/php-model/alias-extends/src/Traits/AliasType.php create mode 100644 seed/php-model/alias-extends/src/Traits/Parent_.php create mode 100644 seed/php-model/alias-extends/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/alias/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/any-auth/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/api-wide-base-path/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/audiences/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/auth-environment-variables/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/basic-auth/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/bytes/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/circular-references-advanced/src/Traits/RootType.php create mode 100644 seed/php-model/circular-references-advanced/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/circular-references/src/Traits/RootType.php create mode 100644 seed/php-model/circular-references/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/cross-package-type-names/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/custom-auth/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/enum/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/error-property/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/examples/src/Types/Traits/Movie.php create mode 100644 seed/php-model/examples/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/exhaustive/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/extends/src/Traits/Docs.php create mode 100644 seed/php-model/extends/src/Traits/ExampleType.php create mode 100644 seed/php-model/extends/src/Traits/Json.php create mode 100644 seed/php-model/extends/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/extra-properties/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/file-download/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/file-upload/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/folders/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/grpc-proto/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/idempotency-headers/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/imdb/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/literal/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/mixed-case/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/mixed-file-directory/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/multi-line-docs/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/multi-url-environment/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/no-environment/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/object/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/objects-with-imports/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/optional/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/package-yml/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/pagination/src/Users/Traits/UserOptionalListPage.php create mode 100644 seed/php-model/pagination/src/Users/Traits/UserPage.php create mode 100644 seed/php-model/pagination/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/plain-text/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/query-parameters/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/reserved-keywords/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/response-property/src/Service/Traits/WithDocs.php create mode 100644 seed/php-model/response-property/src/Traits/WithMetadata.php create mode 100644 seed/php-model/response-property/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/server-sent-events/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/simple-fhir/src/Traits/BaseResource.php create mode 100644 seed/php-model/simple-fhir/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/single-url-environment-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/single-url-environment-no-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/streaming-parameter/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/streaming/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/trace/src/Playlist/Traits/PlaylistCreateRequest.php create mode 100644 seed/php-model/trace/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/undiscriminated-unions/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/unions/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/unknown/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/validation/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/variables/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/version-no-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/version/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/websocket/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/alias-extends/src/Traits/AliasType.php create mode 100644 seed/php-sdk/alias-extends/src/Traits/Parent_.php create mode 100644 seed/php-sdk/alias-extends/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/alias/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/any-auth/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/api-wide-base-path/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/audiences/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/auth-environment-variables/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/basic-auth-environment-variables/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/basic-auth/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/bearer-token-environment-variable/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/bytes/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/circular-references-advanced/src/Traits/RootType.php create mode 100644 seed/php-sdk/circular-references-advanced/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/circular-references/src/Traits/RootType.php create mode 100644 seed/php-sdk/circular-references/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/cross-package-type-names/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/custom-auth/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/enum/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/error-property/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/examples/src/Types/Traits/Movie.php create mode 100644 seed/php-sdk/examples/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/exhaustive/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/extends/src/Traits/Docs.php create mode 100644 seed/php-sdk/extends/src/Traits/ExampleType.php create mode 100644 seed/php-sdk/extends/src/Traits/Json.php create mode 100644 seed/php-sdk/extends/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/extra-properties/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/file-download/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/file-upload/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/folders/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/grpc-proto-exhaustive/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/grpc-proto/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/idempotency-headers/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/imdb/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/literal/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/mixed-case/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/mixed-file-directory/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/multi-line-docs/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/no-environment/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/oauth-client-credentials/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/object/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/objects-with-imports/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/optional/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/package-yml/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/pagination/src/Users/Traits/UserOptionalListPage.php create mode 100644 seed/php-sdk/pagination/src/Users/Traits/UserPage.php create mode 100644 seed/php-sdk/pagination/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/plain-text/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/query-parameters/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/reserved-keywords/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/response-property/src/Service/Traits/WithDocs.php create mode 100644 seed/php-sdk/response-property/src/Traits/WithMetadata.php create mode 100644 seed/php-sdk/response-property/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/server-sent-event-examples/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/server-sent-events/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/simple-fhir/src/Traits/BaseResource.php create mode 100644 seed/php-sdk/simple-fhir/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/single-url-environment-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/single-url-environment-no-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/streaming-parameter/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/streaming/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/trace/src/Playlist/Traits/PlaylistCreateRequest.php create mode 100644 seed/php-sdk/trace/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/undiscriminated-unions/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/unions/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/unknown/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/validation/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/variables/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/version-no-default/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/version/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-sdk/websocket/tests/Seed/Core/Json/TraitTest.php diff --git a/generators/php/codegen/src/AsIs.ts b/generators/php/codegen/src/AsIs.ts index 648935c3bdb..bb332e401a2 100644 --- a/generators/php/codegen/src/AsIs.ts +++ b/generators/php/codegen/src/AsIs.ts @@ -25,6 +25,7 @@ export enum AsIsFiles { DateArrayTypeTest = "Json/DateArrayTypeTest.Template.php", EmptyArraysTest = "Json/EmptyArraysTest.Template.php", EnumTest = "Json/EnumTest.Template.php", + TraitTest = "Json/TraitTest.Template.php", InvalidTypesTest = "Json/InvalidTypesTest.Template.php", MixedDateArrayTypeTest = "Json/MixedDateArrayTypeTest.Template.php", NestedUnionArrayTypeTest = "Json/NestedUnionArrayTypeTest.Template.php", diff --git a/generators/php/codegen/src/asIs/Json/TraitTest.Template.php b/generators/php/codegen/src/asIs/Json/TraitTest.Template.php new file mode 100644 index 00000000000..26178b5ea81 --- /dev/null +++ b/generators/php/codegen/src/asIs/Json/TraitTest.Template.php @@ -0,0 +1,61 @@ +; + +use <%= coreNamespace%>\Json\SerializableType; +use <%= coreNamespace%>\Json\JsonProperty; +use PHPUnit\Framework\TestCase; + +trait IntegerPropertyTrait +{ + /** + * @var int $integerProperty + */ + #[JsonProperty('integer_property')] + public int $integerProperty; +} + +class TypeWithTrait extends SerializableType +{ + use IntegerPropertyTrait; + + /** + * @var string $stringProperty + */ + #[JsonProperty('string_property')] + public string $stringProperty; + + /** + * @param array{ + * integerProperty: int, + * stringProperty: string, + * } $values + */ + public function __construct(array $values) + { + $this->integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} \ No newline at end of file diff --git a/generators/php/codegen/src/ast/Class.ts b/generators/php/codegen/src/ast/Class.ts index abe94e66edf..7b26e5a8813 100644 --- a/generators/php/codegen/src/ast/Class.ts +++ b/generators/php/codegen/src/ast/Class.ts @@ -7,6 +7,8 @@ import { Field } from "./Field"; import { Method } from "./Method"; import { Comment } from "./Comment"; import { orderByAccess } from "./utils/orderByAccess"; +import { ClassReference } from "./ClassReference"; +import { Trait } from "./Trait"; export declare namespace Class { interface Args { @@ -20,6 +22,8 @@ export declare namespace Class { docs?: string; /* The class to inherit from if any */ parentClassReference?: AstNode; + /* The traits that this class uses, if any */ + traits?: ClassReference[]; } interface Constructor { @@ -38,18 +42,20 @@ export class Class extends AstNode { public readonly abstract: boolean; public readonly docs: string | undefined; public readonly parentClassReference: AstNode | undefined; + public readonly traits: ClassReference[]; public readonly fields: Field[] = []; public readonly methods: Method[] = []; private constructor_: Class.Constructor | undefined; - constructor({ name, namespace, abstract, docs, parentClassReference }: Class.Args) { + constructor({ name, namespace, abstract, docs, parentClassReference, traits }: Class.Args) { super(); this.name = name; this.namespace = namespace; this.abstract = abstract ?? false; this.docs = docs; this.parentClassReference = parentClassReference; + this.traits = traits ?? []; } public addConstructor(constructor: Class.Constructor): void { @@ -64,6 +70,10 @@ export class Class extends AstNode { this.methods.push(method); } + public addTrait(traitClassReference: ClassReference): void { + this.traits.push(traitClassReference); + } + public write(writer: Writer): void { if (this.abstract) { writer.write("abstract "); @@ -77,6 +87,17 @@ export class Class extends AstNode { writer.newLine(); writer.writeLine("{"); writer.indent(); + if (this.traits.length > 0) { + writer.write("use "); + this.traits.forEach((trait, index) => { + if (index > 0) { + writer.write(","); + } + writer.writeNode(trait); + }); + writer.writeTextStatement(""); + writer.newLine(); + } this.writeFields({ writer, fields: orderByAccess(this.fields) }); if (this.constructor != null || this.methods.length > 0) { @@ -141,13 +162,15 @@ export class Class extends AstNode { } private writeFields({ writer, fields }: { writer: Writer; fields: Field[] }): void { - fields.forEach((field, index) => { - if (index > 0) { - writer.newLine(); - } - field.write(writer); - writer.writeNewLineIfLastLineNot(); - }); + fields + .filter((field) => !field.inherited) + .forEach((field, index) => { + if (index > 0) { + writer.newLine(); + } + field.write(writer); + writer.writeNewLineIfLastLineNot(); + }); } private writeMethods({ writer, methods }: { writer: Writer; methods: Method[] }): void { diff --git a/generators/php/codegen/src/ast/DataClass.ts b/generators/php/codegen/src/ast/DataClass.ts index f3c5e3efa9a..f2e4b3a6f8c 100644 --- a/generators/php/codegen/src/ast/DataClass.ts +++ b/generators/php/codegen/src/ast/DataClass.ts @@ -9,6 +9,8 @@ import { Type } from "./Type"; import { orderByAccess } from "./utils/orderByAccess"; import { php } from ".."; import { convertFromPhpVariableName } from "./utils/convertFromPhpVariableName"; +import { Trait } from "./Trait"; +import { ClassReference } from "../php"; const CONSTRUCTOR_PARAMETER_NAME = "values"; @@ -21,11 +23,11 @@ export class DataClass extends AstNode { public readonly namespace: string; private class_: Class; - constructor({ name, namespace, abstract, docs, parentClassReference }: DataClass.Args) { + constructor({ name, namespace, abstract, docs, parentClassReference, traits }: DataClass.Args) { super(); this.name = name; this.namespace = namespace; - this.class_ = new Class({ name, namespace, abstract, docs, parentClassReference }); + this.class_ = new Class({ name, namespace, abstract, docs, parentClassReference, traits }); } public addField(field: Field): void { @@ -35,6 +37,9 @@ export class DataClass extends AstNode { public addMethod(method: Method): void { this.class_.addMethod(method); } + public addTrait(traitClassRefeference: ClassReference): void { + this.class_.addTrait(traitClassRefeference); + } public write(writer: Writer): void { const orderedFields = orderByAccess(this.class_.fields).map( diff --git a/generators/php/codegen/src/ast/Field.ts b/generators/php/codegen/src/ast/Field.ts index 8675cb4a10c..fd836a7f359 100644 --- a/generators/php/codegen/src/ast/Field.ts +++ b/generators/php/codegen/src/ast/Field.ts @@ -25,6 +25,8 @@ export declare namespace Field { inlineDocs?: string; /* Field attributes */ attributes?: Attribute[]; + /* Indicates that this field is inherited and should not be written to the class. */ + inherited?: boolean; } } @@ -37,8 +39,9 @@ export class Field extends AstNode { private docs: string | undefined; private inlineDocs: string | undefined; private attributes: Attribute[]; + public readonly inherited: boolean; - constructor({ name, type, access, readonly_, initializer, docs, inlineDocs, attributes }: Field.Args) { + constructor({ name, type, access, readonly_, initializer, docs, inlineDocs, attributes, inherited }: Field.Args) { super(); this.name = convertToPhpVariableName(name); this.type = type; @@ -48,6 +51,7 @@ export class Field extends AstNode { this.docs = docs; this.inlineDocs = inlineDocs; this.attributes = attributes ?? []; + this.inherited = inherited ?? false; } public write(writer: Writer): void { diff --git a/generators/php/codegen/src/ast/Trait.ts b/generators/php/codegen/src/ast/Trait.ts new file mode 100644 index 00000000000..a3a10a2264b --- /dev/null +++ b/generators/php/codegen/src/ast/Trait.ts @@ -0,0 +1,101 @@ +import { AstNode } from "./core/AstNode"; +import { Writer } from "./core/Writer"; +import { Field } from "./Field"; +import { Method } from "./Method"; +import { Comment } from "./Comment"; +import { orderByAccess } from "./utils/orderByAccess"; +import { ClassReference } from "./ClassReference"; + +export declare namespace Trait { + interface Args { + /* The name of the PHP trait */ + name: string; + /* The namespace of the PHP trait */ + namespace: string; + /* Docs associated with the trait */ + docs?: string; + /* The traits that this trait uses, if any */ + traits?: ClassReference[]; + } +} + +export class Trait extends AstNode { + public readonly name: string; + public readonly namespace: string; + public readonly docs: string | undefined; + public readonly traits: ClassReference[]; + + public readonly fields: Field[] = []; + public readonly methods: Method[] = []; + + constructor({ name, namespace, docs, traits }: Trait.Args) { + super(); + this.name = name; + this.namespace = namespace; + this.docs = docs; + this.traits = traits ?? []; + } + + public addField(field: Field): void { + this.fields.push(field); + } + + public addMethod(method: Method): void { + this.methods.push(method); + } + + public write(writer: Writer): void { + this.writeComment(writer); + writer.write(`trait ${this.name} `); + writer.newLine(); + writer.writeLine("{"); + writer.indent(); + + if (this.traits.length > 0) { + writer.write("use "); + this.traits.forEach((trait, index) => { + if (index > 0) { + writer.write(","); + } + writer.writeNode(trait); + }); + writer.writeTextStatement(""); + writer.newLine(); + } + + this.writeFields({ writer, fields: orderByAccess(this.fields) }); + this.writeMethods({ writer, methods: orderByAccess(this.methods) }); + + writer.dedent(); + writer.writeLine("}"); + return; + } + + private writeComment(writer: Writer): void { + if (this.docs == null) { + return undefined; + } + const comment = new Comment({ docs: this.docs }); + comment.write(writer); + } + + private writeFields({ writer, fields }: { writer: Writer; fields: Field[] }): void { + fields.forEach((field, index) => { + if (index > 0) { + writer.newLine(); + } + field.write(writer); + writer.writeNewLineIfLastLineNot(); + }); + } + + private writeMethods({ writer, methods }: { writer: Writer; methods: Method[] }): void { + methods.forEach((method, index) => { + if (index > 0) { + writer.newLine(); + } + method.write(writer); + writer.writeNewLineIfLastLineNot(); + }); + } +} diff --git a/generators/php/codegen/src/ast/index.ts b/generators/php/codegen/src/ast/index.ts index f681bee66e4..eeec5368649 100644 --- a/generators/php/codegen/src/ast/index.ts +++ b/generators/php/codegen/src/ast/index.ts @@ -1,6 +1,7 @@ export { Array_ as Array } from "./Array"; export { Attribute } from "./Attribute"; export { Class } from "./Class"; +export { Trait } from "./Trait"; export { ClassInstantiation } from "./ClassInstantiation"; export { ClassReference } from "./ClassReference"; export { CodeBlock } from "./CodeBlock"; diff --git a/generators/php/codegen/src/constants.ts b/generators/php/codegen/src/constants.ts new file mode 100644 index 00000000000..80d5bc5b5a8 --- /dev/null +++ b/generators/php/codegen/src/constants.ts @@ -0,0 +1 @@ +export const TRAITS_DIRECTORY = "Traits"; diff --git a/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts b/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts index 12460deb336..a707a65234b 100644 --- a/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts +++ b/generators/php/codegen/src/context/AbstractPhpGeneratorContext.ts @@ -9,7 +9,8 @@ import { Subpackage, SubpackageId, FernFilepath, - PrimitiveTypeV1 + PrimitiveTypeV1, + ObjectTypeDeclaration } from "@fern-fern/ir-sdk/api"; import { BasePhpCustomConfigSchema } from "../custom-config/BasePhpCustomConfigSchema"; import { PhpProject } from "../project"; @@ -20,6 +21,7 @@ import { AsIsFiles } from "../AsIs"; import { RelativeFilePath } from "@fern-api/fs-utils"; import { php } from ".."; import { GLOBAL_NAMESPACE } from "../ast/core/Constant"; +import { TRAITS_DIRECTORY } from "../constants"; export interface FileLocation { namespace: string; @@ -283,6 +285,35 @@ export abstract class AbstractPhpGeneratorContext< } } + public getUnderlyingObjectTypeDeclaration(typeReference: TypeReference): ObjectTypeDeclaration { + switch (typeReference.type) { + case "named": { + const declaration = this.getTypeDeclarationOrThrow(typeReference.typeId); + if (declaration.shape.type === "alias") { + return this.getUnderlyingObjectTypeDeclaration(declaration.shape.aliasOf); + } + if (declaration.shape.type === "object") { + return declaration.shape; + } + throw new Error("Type is not an object type"); + } + case "primitive": + case "unknown": + case "container": + } + throw new Error("Type is not an object type"); + } + + public getUnderlyingObjectTypeDeclarationOrThrow(typeDeclaration: TypeDeclaration): ObjectTypeDeclaration { + if (typeDeclaration.shape.type === "alias") { + return this.getUnderlyingObjectTypeDeclaration(typeDeclaration.shape.aliasOf); + } + if (typeDeclaration.shape.type === "object") { + return typeDeclaration.shape; + } + throw new Error("Type is not an object type"); + } + public maybeLiteral(typeReference: TypeReference): Literal | undefined { if (typeReference.type === "container" && typeReference.container.type === "literal") { return typeReference.container.literal; @@ -327,6 +358,7 @@ export abstract class AbstractPhpGeneratorContext< AsIsFiles.DateArrayTypeTest, AsIsFiles.EmptyArraysTest, AsIsFiles.InvalidTypesTest, + AsIsFiles.TraitTest, AsIsFiles.MixedDateArrayTypeTest, AsIsFiles.NestedUnionArrayTypeTest, AsIsFiles.NullableArrayTypeTest, @@ -341,6 +373,11 @@ export abstract class AbstractPhpGeneratorContext< public abstract getLocationForTypeId(typeId: TypeId): FileLocation; + public getTraitLocationForTypeId(typeId: TypeId): FileLocation { + const typeDeclaration = this.getTypeDeclarationOrThrow(typeId); + return this.getFileLocation(typeDeclaration.name.fernFilepath, TRAITS_DIRECTORY); + } + protected getFileLocation(filepath: FernFilepath, suffix?: string): FileLocation { let parts = filepath.allParts.map((path) => path.pascalCase.safeName); parts = suffix != null ? [...parts, suffix] : parts; diff --git a/generators/php/codegen/src/context/PhpTypeMapper.ts b/generators/php/codegen/src/context/PhpTypeMapper.ts index 305d5836886..ba5d9b4780e 100644 --- a/generators/php/codegen/src/context/PhpTypeMapper.ts +++ b/generators/php/codegen/src/context/PhpTypeMapper.ts @@ -58,6 +58,13 @@ export class PhpTypeMapper { }); } + public convertToTraitClassReference(declaredTypeName: { typeId: TypeId; name: Name }): ClassReference { + return new php.ClassReference({ + name: this.context.getClassName(declaredTypeName.name), + namespace: this.context.getTraitLocationForTypeId(declaredTypeName.typeId).namespace + }); + } + private convertContainer({ container, preserveEnums }: { container: ContainerType; preserveEnums: boolean }): Type { switch (container.type) { case "list": diff --git a/generators/php/codegen/src/php.ts b/generators/php/codegen/src/php.ts index f96f5f538df..3536da4f6f8 100644 --- a/generators/php/codegen/src/php.ts +++ b/generators/php/codegen/src/php.ts @@ -3,6 +3,7 @@ import { Array as Array_, Attribute, Class, + Trait, ClassInstantiation, ClassReference, CodeBlock, @@ -27,6 +28,10 @@ export function class_(args: Class.Args): Class { return new Class(args); } +export function trait(args: Trait.Args): Trait { + return new Trait(args); +} + export function classReference(args: ClassReference.Args): ClassReference { return new ClassReference(args); } @@ -76,6 +81,7 @@ export { Array, Attribute, Class, + Trait, ClassInstantiation, ClassReference, CodeBlock, diff --git a/generators/php/codegen/src/project/PhpFile.ts b/generators/php/codegen/src/project/PhpFile.ts index 7603120518a..bd584e4d1f2 100644 --- a/generators/php/codegen/src/project/PhpFile.ts +++ b/generators/php/codegen/src/project/PhpFile.ts @@ -4,7 +4,7 @@ import path from "path"; import { BasePhpCustomConfigSchema } from "../custom-config/BasePhpCustomConfigSchema"; import { File } from "@fern-api/generator-commons"; import { Class } from "../ast/Class"; -import { Enum } from "../ast"; +import { Enum, Trait } from "../ast"; import { DataClass } from "../ast/DataClass"; export type Namespace = string; @@ -12,7 +12,7 @@ export type Namespace = string; export declare namespace PhpFile { interface Args { /* The class to be written to the PHP File */ - clazz: Class | DataClass | Enum; + clazz: Class | DataClass | Trait | Enum; /* Directory of the filepath */ directory: RelativeFilePath; /* The root namespace of the project. Can be pulled directly from context. */ @@ -49,7 +49,7 @@ function phpFileContent({ rootNamespace, customConfig }: { - clazz: Class | DataClass | Enum; + clazz: Class | DataClass | Trait | Enum; rootNamespace: string; customConfig: BasePhpCustomConfigSchema; }): string { diff --git a/generators/php/model/src/ModelGeneratorCli.ts b/generators/php/model/src/ModelGeneratorCli.ts index ccf11dfd6fd..1307d0bca5b 100644 --- a/generators/php/model/src/ModelGeneratorCli.ts +++ b/generators/php/model/src/ModelGeneratorCli.ts @@ -4,6 +4,7 @@ import { IntermediateRepresentation } from "@fern-fern/ir-sdk/api"; import { ModelCustomConfigSchema } from "./ModelCustomConfig"; import { ModelGeneratorContext } from "./ModelGeneratorContext"; import { generateModels } from "./generateModels"; +import { generateTraits } from "./generateTraits"; export class ModelGeneratorCLI extends AbstractPhpGeneratorCli { protected constructContext({ @@ -38,6 +39,7 @@ export class ModelGeneratorCLI extends AbstractPhpGeneratorCli { generateModels(context); + generateTraits(context); await context.project.persist(); } } diff --git a/generators/php/model/src/generateTraits.ts b/generators/php/model/src/generateTraits.ts new file mode 100644 index 00000000000..a06f26bfc68 --- /dev/null +++ b/generators/php/model/src/generateTraits.ts @@ -0,0 +1,28 @@ +import { ModelGeneratorContext } from "./ModelGeneratorContext"; +import { TraitGenerator } from "./trait/TraitGenerator"; + +export function generateTraits(context: ModelGeneratorContext): void { + const extendedTypeIdsFromTypes = Object.values(context.ir.types).flatMap((typeDeclaration) => + typeDeclaration.shape._visit({ + alias: () => [], + enum: () => [], + object: (objectDeclaration) => objectDeclaration.extends.map((declaredTypeName) => declaredTypeName.typeId), + undiscriminatedUnion: () => [], + union: () => [], + _other: () => [] + }) + ); + const extendedTypeIdsFromInlinedRequests = Object.values(context.ir.services) + .flatMap((service) => service.endpoints) + .flatMap((endpoint) => { + return endpoint.requestBody?.type === "inlinedRequestBody" + ? endpoint.requestBody.extends.map((declaredTypeName) => declaredTypeName.typeId) + : []; + }); + for (const typeId of new Set([...extendedTypeIdsFromTypes, ...extendedTypeIdsFromInlinedRequests])) { + const typeDeclaration = context.getTypeDeclarationOrThrow(typeId); + const objectTypeDeclaration = context.getUnderlyingObjectTypeDeclarationOrThrow(typeDeclaration); + const file = new TraitGenerator(context, typeDeclaration, objectTypeDeclaration).generate(); + context.project.addSourceFiles(file); + } +} diff --git a/generators/php/model/src/index.ts b/generators/php/model/src/index.ts index 14e382f1a72..f7ba0831e8d 100644 --- a/generators/php/model/src/index.ts +++ b/generators/php/model/src/index.ts @@ -1,2 +1,3 @@ export * from "./ModelGeneratorCli"; export { generateModels } from "./generateModels"; +export { generateTraits } from "./generateTraits"; diff --git a/generators/php/model/src/object/ObjectGenerator.ts b/generators/php/model/src/object/ObjectGenerator.ts index 3437940bc93..719b6257f8a 100644 --- a/generators/php/model/src/object/ObjectGenerator.ts +++ b/generators/php/model/src/object/ObjectGenerator.ts @@ -2,7 +2,7 @@ import { join, RelativeFilePath } from "@fern-api/fs-utils"; import { PhpFile } from "@fern-api/php-codegen"; import { FileGenerator } from "@fern-api/php-codegen"; import { php } from "@fern-api/php-codegen"; -import { ObjectTypeDeclaration, TypeDeclaration } from "@fern-fern/ir-sdk/api"; +import { ObjectProperty, ObjectTypeDeclaration, TypeDeclaration } from "@fern-fern/ir-sdk/api"; import { ModelCustomConfigSchema } from "../ModelCustomConfig"; import { ModelGeneratorContext } from "../ModelGeneratorContext"; @@ -23,24 +23,17 @@ export class ObjectGenerator extends FileGenerator + this.context.phpTypeMapper.convertToTraitClassReference(declaredTypeName) + ) }); - // TODO: handle extended properties for (const property of this.objectDeclaration.properties) { - const convertedType = this.context.phpTypeMapper.convert({ reference: property.valueType }); - clazz.addField( - php.field({ - type: convertedType, - name: this.context.getPropertyName(property.name.name), - access: "public", - docs: property.docs, - attributes: this.context.phpAttributeMapper.convert({ - type: convertedType, - property - }) - }) - ); + clazz.addField(this.toField({ property })); + } + for (const property of this.objectDeclaration.extendedProperties ?? []) { + clazz.addField(this.toField({ property, inherited: true })); } return new PhpFile({ @@ -51,6 +44,21 @@ export class ObjectGenerator extends FileGenerator { + private readonly typeDeclaration: TypeDeclaration; + private readonly classReference: php.ClassReference; + constructor( + context: ModelGeneratorContext, + typeDeclaration: TypeDeclaration, + private readonly objectDeclaration: ObjectTypeDeclaration + ) { + super(context); + this.typeDeclaration = typeDeclaration; + this.classReference = this.context.phpTypeMapper.convertToTraitClassReference(this.typeDeclaration.name); + } + + public doGenerate(): PhpFile { + const clazz = php.trait({ + ...this.classReference, + docs: this.typeDeclaration.docs, + traits: this.objectDeclaration.extends.map((declaredTypeName) => + this.context.phpTypeMapper.convertToTraitClassReference(declaredTypeName) + ) + }); + + for (const property of this.objectDeclaration.properties) { + const convertedType = this.context.phpTypeMapper.convert({ reference: property.valueType }); + clazz.addField( + php.field({ + type: convertedType, + name: this.context.getPropertyName(property.name.name), + access: "public", + docs: property.docs, + attributes: this.context.phpAttributeMapper.convert({ + type: convertedType, + property + }) + }) + ); + } + + return new PhpFile({ + clazz, + rootNamespace: this.context.getRootNamespace(), + directory: this.context.getTraitLocationForTypeId(this.typeDeclaration.name.typeId).directory, + customConfig: this.context.customConfig + }); + } + + protected getFilepath(): RelativeFilePath { + return this.context.getTraitLocationForTypeId(this.typeDeclaration.name.typeId).directory; + } +} diff --git a/generators/php/sdk/src/SdkGeneratorCli.ts b/generators/php/sdk/src/SdkGeneratorCli.ts index 31628921fdb..5c77b1f7dfe 100644 --- a/generators/php/sdk/src/SdkGeneratorCli.ts +++ b/generators/php/sdk/src/SdkGeneratorCli.ts @@ -4,7 +4,7 @@ import { FernGeneratorExec } from "@fern-fern/generator-exec-sdk"; import { HttpService, IntermediateRepresentation } from "@fern-fern/ir-sdk/api"; import { SdkCustomConfigSchema } from "./SdkCustomConfig"; import { SdkGeneratorContext } from "./SdkGeneratorContext"; -import { generateModels } from "@fern-api/php-model"; +import { generateModels, generateTraits } from "@fern-api/php-model"; import { RootClientGenerator } from "./root-client/RootClientGenerator"; import { SubPackageClientGenerator } from "./subpackage-client/SubPackageClientGenerator"; import { WrappedEndpointRequestGenerator } from "./endpoint/request/WrappedEndpointRequestGenerator"; @@ -49,6 +49,7 @@ export class SdkGeneratorCLI extends AbstractPhpGeneratorCli { generateModels(context); + generateTraits(context); this.generateRootClient(context); this.generateSubpackages(context); this.generateEnvironment(context); diff --git a/generators/php/sdk/src/endpoint/request/WrappedEndpointRequestGenerator.ts b/generators/php/sdk/src/endpoint/request/WrappedEndpointRequestGenerator.ts index f25032cfa60..787fdf30b0c 100644 --- a/generators/php/sdk/src/endpoint/request/WrappedEndpointRequestGenerator.ts +++ b/generators/php/sdk/src/endpoint/request/WrappedEndpointRequestGenerator.ts @@ -1,6 +1,12 @@ import { php, PhpFile, FileGenerator, FileLocation } from "@fern-api/php-codegen"; import { join, RelativeFilePath } from "@fern-api/fs-utils"; -import { HttpEndpoint, SdkRequestWrapper, ServiceId } from "@fern-fern/ir-sdk/api"; +import { + HttpEndpoint, + InlinedRequestBodyProperty, + ObjectProperty, + SdkRequestWrapper, + ServiceId +} from "@fern-fern/ir-sdk/api"; import { SdkCustomConfigSchema } from "../../SdkCustomConfig"; import { SdkGeneratorContext } from "../../SdkGeneratorContext"; @@ -78,16 +84,13 @@ export class WrappedEndpointRequestGenerator extends FileGenerator< }, inlinedRequestBody: (request) => { for (const property of request.properties) { - const type = this.context.phpTypeMapper.convert({ reference: property.valueType }); - clazz.addField( - php.field({ - name: this.context.getPropertyName(property.name.name), - type, - access: "public", - docs: property.docs, - attributes: this.context.phpAttributeMapper.convert({ type, property }) - }) - ); + clazz.addField(this.toField({ property })); + } + for (const property of request.extendedProperties ?? []) { + clazz.addField(this.toField({ property, inherited: true })); + } + for (const declaredTypeName of request.extends) { + clazz.addTrait(this.context.phpTypeMapper.convertToTraitClassReference(declaredTypeName)); } }, fileUpload: () => undefined, @@ -103,6 +106,21 @@ export class WrappedEndpointRequestGenerator extends FileGenerator< }); } + private toField({ property, inherited }: { property: InlinedRequestBodyProperty; inherited?: boolean }): php.Field { + const convertedType = this.context.phpTypeMapper.convert({ reference: property.valueType }); + return php.field({ + type: convertedType, + name: this.context.getPropertyName(property.name.name), + access: "public", + docs: property.docs, + attributes: this.context.phpAttributeMapper.convert({ + type: convertedType, + property + }), + inherited + }); + } + protected getFilepath(): RelativeFilePath { return join( this.context.project.filepaths.getSourceDirectory(), diff --git a/generators/php/sdk/versions.yml b/generators/php/sdk/versions.yml index c9a63e645a9..736c756accb 100644 --- a/generators/php/sdk/versions.yml +++ b/generators/php/sdk/versions.yml @@ -1,3 +1,8 @@ +- version: 0.1.6 + changelogEntry: + - type: feat + summary: >- + Support inheritance for types and inlined requests. - version: 0.1.5 changelogEntry: - type: feat diff --git a/seed/php-model/alias-extends/src/Child.php b/seed/php-model/alias-extends/src/Child.php index b8c31ff899d..7bee8fcda65 100644 --- a/seed/php-model/alias-extends/src/Child.php +++ b/seed/php-model/alias-extends/src/Child.php @@ -3,10 +3,13 @@ namespace Seed; use Seed\Core\Json\SerializableType; +use Seed\Traits\Parent_; use Seed\Core\Json\JsonProperty; class Child extends SerializableType { + use Parent_; + /** * @var string $child */ @@ -16,11 +19,13 @@ class Child extends SerializableType /** * @param array{ * child: string, + * parent: string, * } $values */ public function __construct( array $values, ) { $this->child = $values['child']; + $this->parent = $values['parent']; } } diff --git a/seed/php-model/alias-extends/src/Traits/AliasType.php b/seed/php-model/alias-extends/src/Traits/AliasType.php new file mode 100644 index 00000000000..6ec816f3e3a --- /dev/null +++ b/seed/php-model/alias-extends/src/Traits/AliasType.php @@ -0,0 +1,14 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/alias/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/alias/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/alias/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/any-auth/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/any-auth/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/any-auth/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/api-wide-base-path/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/audiences/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/audiences/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/audiences/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/auth-environment-variables/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/basic-auth-environment-variables/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/basic-auth/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/basic-auth/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/basic-auth/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/bearer-token-environment-variable/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/bytes/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/bytes/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/bytes/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/circular-references-advanced/src/A/A.php b/seed/php-model/circular-references-advanced/src/A/A.php index ad86a0bec70..4cb6b867841 100644 --- a/seed/php-model/circular-references-advanced/src/A/A.php +++ b/seed/php-model/circular-references-advanced/src/A/A.php @@ -3,7 +3,21 @@ namespace Seed\A; use Seed\Core\Json\SerializableType; +use Seed\Traits\RootType; class A extends SerializableType { + use RootType; + + + /** + * @param array{ + * s: string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->s = $values['s']; + } } diff --git a/seed/php-model/circular-references-advanced/src/Traits/RootType.php b/seed/php-model/circular-references-advanced/src/Traits/RootType.php new file mode 100644 index 00000000000..44f28a09118 --- /dev/null +++ b/seed/php-model/circular-references-advanced/src/Traits/RootType.php @@ -0,0 +1,14 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/circular-references/src/A/A.php b/seed/php-model/circular-references/src/A/A.php index ad86a0bec70..4cb6b867841 100644 --- a/seed/php-model/circular-references/src/A/A.php +++ b/seed/php-model/circular-references/src/A/A.php @@ -3,7 +3,21 @@ namespace Seed\A; use Seed\Core\Json\SerializableType; +use Seed\Traits\RootType; class A extends SerializableType { + use RootType; + + + /** + * @param array{ + * s: string, + * } $values + */ + public function __construct( + array $values, + ) { + $this->s = $values['s']; + } } diff --git a/seed/php-model/circular-references/src/Traits/RootType.php b/seed/php-model/circular-references/src/Traits/RootType.php new file mode 100644 index 00000000000..44f28a09118 --- /dev/null +++ b/seed/php-model/circular-references/src/Traits/RootType.php @@ -0,0 +1,14 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/cross-package-type-names/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/custom-auth/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/custom-auth/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/custom-auth/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/enum/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/enum/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/enum/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/error-property/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/error-property/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/error-property/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/examples/src/Types/ExtendedMovie.php b/seed/php-model/examples/src/Types/ExtendedMovie.php index 4c313c65c61..03ac0fe7d8a 100644 --- a/seed/php-model/examples/src/Types/ExtendedMovie.php +++ b/seed/php-model/examples/src/Types/ExtendedMovie.php @@ -3,11 +3,14 @@ namespace Seed\Types; use Seed\Core\Json\SerializableType; +use Seed\Types\Traits\Movie; use Seed\Core\Json\JsonProperty; use Seed\Core\Types\ArrayType; class ExtendedMovie extends SerializableType { + use Movie; + /** * @var array $cast */ @@ -17,11 +20,31 @@ class ExtendedMovie extends SerializableType /** * @param array{ * cast: array, + * id: string, + * prequel?: ?string, + * title: string, + * from: string, + * rating: float, + * type: string, + * tag: string, + * book?: ?string, + * metadata: array, + * revenue: int, * } $values */ public function __construct( array $values, ) { $this->cast = $values['cast']; + $this->id = $values['id']; + $this->prequel = $values['prequel'] ?? null; + $this->title = $values['title']; + $this->from = $values['from']; + $this->rating = $values['rating']; + $this->type = $values['type']; + $this->tag = $values['tag']; + $this->book = $values['book'] ?? null; + $this->metadata = $values['metadata']; + $this->revenue = $values['revenue']; } } diff --git a/seed/php-model/examples/src/Types/Traits/Movie.php b/seed/php-model/examples/src/Types/Traits/Movie.php new file mode 100644 index 00000000000..2f40680e303 --- /dev/null +++ b/seed/php-model/examples/src/Types/Traits/Movie.php @@ -0,0 +1,69 @@ + $metadata + */ + #[JsonProperty('metadata'), ArrayType(['string' => 'mixed'])] + public array $metadata; + + /** + * @var int $revenue + */ + #[JsonProperty('revenue')] + public int $revenue; +} diff --git a/seed/php-model/examples/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/examples/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/examples/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/exhaustive/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/exhaustive/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/exhaustive/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/extends/src/ExampleType.php b/seed/php-model/extends/src/ExampleType.php index 7bc916aea74..f1d3ea4de79 100644 --- a/seed/php-model/extends/src/ExampleType.php +++ b/seed/php-model/extends/src/ExampleType.php @@ -3,10 +3,13 @@ namespace Seed; use Seed\Core\Json\SerializableType; +use Seed\Traits\Docs; use Seed\Core\Json\JsonProperty; class ExampleType extends SerializableType { + use Docs; + /** * @var string $name */ @@ -16,11 +19,13 @@ class ExampleType extends SerializableType /** * @param array{ * name: string, + * docs: string, * } $values */ public function __construct( array $values, ) { $this->name = $values['name']; + $this->docs = $values['docs']; } } diff --git a/seed/php-model/extends/src/Json.php b/seed/php-model/extends/src/Json.php index 9bc11b9c494..c62a6c629f3 100644 --- a/seed/php-model/extends/src/Json.php +++ b/seed/php-model/extends/src/Json.php @@ -3,10 +3,13 @@ namespace Seed; use Seed\Core\Json\SerializableType; +use Seed\Traits\Docs; use Seed\Core\Json\JsonProperty; class Json extends SerializableType { + use Docs; + /** * @var string $raw */ @@ -16,11 +19,13 @@ class Json extends SerializableType /** * @param array{ * raw: string, + * docs: string, * } $values */ public function __construct( array $values, ) { $this->raw = $values['raw']; + $this->docs = $values['docs']; } } diff --git a/seed/php-model/extends/src/NestedType.php b/seed/php-model/extends/src/NestedType.php index 2955b61bdc5..74cecd3ca25 100644 --- a/seed/php-model/extends/src/NestedType.php +++ b/seed/php-model/extends/src/NestedType.php @@ -3,10 +3,13 @@ namespace Seed; use Seed\Core\Json\SerializableType; +use Seed\Traits\Json; use Seed\Core\Json\JsonProperty; class NestedType extends SerializableType { + use Json; + /** * @var string $name */ @@ -16,11 +19,15 @@ class NestedType extends SerializableType /** * @param array{ * name: string, + * raw: string, + * docs: string, * } $values */ public function __construct( array $values, ) { $this->name = $values['name']; + $this->raw = $values['raw']; + $this->docs = $values['docs']; } } diff --git a/seed/php-model/extends/src/Traits/Docs.php b/seed/php-model/extends/src/Traits/Docs.php new file mode 100644 index 00000000000..17eaf8aa4ff --- /dev/null +++ b/seed/php-model/extends/src/Traits/Docs.php @@ -0,0 +1,14 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/extra-properties/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/extra-properties/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/extra-properties/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/file-download/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/file-download/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/file-download/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/file-upload/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/file-upload/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/file-upload/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/folders/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/folders/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/folders/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/grpc-proto-exhaustive/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/grpc-proto/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/grpc-proto/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/grpc-proto/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/idempotency-headers/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/idempotency-headers/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/imdb/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/imdb/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/imdb/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/literal/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/literal/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/literal/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/mixed-case/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/mixed-case/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/mixed-case/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/mixed-file-directory/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/multi-line-docs/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/multi-line-docs/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/multi-url-environment-no-default/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/multi-url-environment/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/multi-url-environment/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/no-environment/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/no-environment/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/no-environment/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/oauth-client-credentials-default/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/oauth-client-credentials-environment-variables/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/oauth-client-credentials-nested-root/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/oauth-client-credentials/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/object/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/object/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/object/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/objects-with-imports/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/objects-with-imports/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/optional/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/optional/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/optional/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/package-yml/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/package-yml/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/package-yml/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/pagination/src/Users/ListUsersExtendedOptionalListResponse.php b/seed/php-model/pagination/src/Users/ListUsersExtendedOptionalListResponse.php index 00810b43f46..e454dd175bf 100644 --- a/seed/php-model/pagination/src/Users/ListUsersExtendedOptionalListResponse.php +++ b/seed/php-model/pagination/src/Users/ListUsersExtendedOptionalListResponse.php @@ -3,10 +3,13 @@ namespace Seed\Users; use Seed\Core\Json\SerializableType; +use Seed\Users\Traits\UserOptionalListPage; use Seed\Core\Json\JsonProperty; class ListUsersExtendedOptionalListResponse extends SerializableType { + use UserOptionalListPage; + /** * @var int $totalCount The totall number of /users */ @@ -16,11 +19,15 @@ class ListUsersExtendedOptionalListResponse extends SerializableType /** * @param array{ * totalCount: int, + * data: UserOptionalListContainer, + * next?: ?string, * } $values */ public function __construct( array $values, ) { $this->totalCount = $values['totalCount']; + $this->data = $values['data']; + $this->next = $values['next'] ?? null; } } diff --git a/seed/php-model/pagination/src/Users/ListUsersExtendedResponse.php b/seed/php-model/pagination/src/Users/ListUsersExtendedResponse.php index dc600ccd164..93df8c38ec8 100644 --- a/seed/php-model/pagination/src/Users/ListUsersExtendedResponse.php +++ b/seed/php-model/pagination/src/Users/ListUsersExtendedResponse.php @@ -3,10 +3,13 @@ namespace Seed\Users; use Seed\Core\Json\SerializableType; +use Seed\Users\Traits\UserPage; use Seed\Core\Json\JsonProperty; class ListUsersExtendedResponse extends SerializableType { + use UserPage; + /** * @var int $totalCount The totall number of /users */ @@ -16,11 +19,15 @@ class ListUsersExtendedResponse extends SerializableType /** * @param array{ * totalCount: int, + * data: UserListContainer, + * next?: ?string, * } $values */ public function __construct( array $values, ) { $this->totalCount = $values['totalCount']; + $this->data = $values['data']; + $this->next = $values['next'] ?? null; } } diff --git a/seed/php-model/pagination/src/Users/Traits/UserOptionalListPage.php b/seed/php-model/pagination/src/Users/Traits/UserOptionalListPage.php new file mode 100644 index 00000000000..3056830dc17 --- /dev/null +++ b/seed/php-model/pagination/src/Users/Traits/UserOptionalListPage.php @@ -0,0 +1,21 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/plain-text/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/plain-text/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/plain-text/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/query-parameters/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/query-parameters/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/query-parameters/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/reserved-keywords/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/reserved-keywords/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/response-property/src/Service/Response.php b/seed/php-model/response-property/src/Service/Response.php index 46eaf9deee5..ec80232ba7a 100644 --- a/seed/php-model/response-property/src/Service/Response.php +++ b/seed/php-model/response-property/src/Service/Response.php @@ -3,10 +3,15 @@ namespace Seed\Service; use Seed\Core\Json\SerializableType; +use Seed\Traits\WithMetadata; +use Seed\Service\Traits\WithDocs; use Seed\Core\Json\JsonProperty; class Response extends SerializableType { + use WithMetadata; + use WithDocs; + /** * @var Movie $data */ @@ -16,11 +21,15 @@ class Response extends SerializableType /** * @param array{ * data: Movie, + * metadata: array, + * docs: string, * } $values */ public function __construct( array $values, ) { $this->data = $values['data']; + $this->metadata = $values['metadata']; + $this->docs = $values['docs']; } } diff --git a/seed/php-model/response-property/src/Service/Traits/WithDocs.php b/seed/php-model/response-property/src/Service/Traits/WithDocs.php new file mode 100644 index 00000000000..429bb4dd850 --- /dev/null +++ b/seed/php-model/response-property/src/Service/Traits/WithDocs.php @@ -0,0 +1,14 @@ + $metadata + */ + #[JsonProperty('metadata'), ArrayType(['string' => 'string'])] + public array $metadata; +} diff --git a/seed/php-model/response-property/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/response-property/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/response-property/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/server-sent-event-examples/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/server-sent-events/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/server-sent-events/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..e23e5c049cc --- /dev/null +++ b/seed/php-model/server-sent-events/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,61 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $data = [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ]; + + $json = json_encode($data, JSON_THROW_ON_ERROR); + + $object = TypeWithTrait::fromJson($json); + + $serializedJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + } +} diff --git a/seed/php-model/simple-fhir/src/Account.php b/seed/php-model/simple-fhir/src/Account.php index d845b0822f6..422d2ee66b5 100644 --- a/seed/php-model/simple-fhir/src/Account.php +++ b/seed/php-model/simple-fhir/src/Account.php @@ -3,10 +3,13 @@ namespace Seed; use Seed\Core\Json\SerializableType; +use Seed\Traits\BaseResource; use Seed\Core\Json\JsonProperty; class Account extends SerializableType { + use BaseResource; + /** * @var string $resourceType */ @@ -37,6 +40,9 @@ class Account extends SerializableType * name: string, * patient?: ?Patient, * practitioner?: ?Practitioner, + * id: string, + * relatedResources: array, + * memo: Memo, * } $values */ public function __construct( @@ -46,5 +52,8 @@ public function __construct( $this->name = $values['name']; $this->patient = $values['patient'] ?? null; $this->practitioner = $values['practitioner'] ?? null; + $this->id = $values['id']; + $this->relatedResources = $values['relatedResources']; + $this->memo = $values['memo']; } } diff --git a/seed/php-model/simple-fhir/src/Patient.php b/seed/php-model/simple-fhir/src/Patient.php index 277551e5508..bb245c25d64 100644 --- a/seed/php-model/simple-fhir/src/Patient.php +++ b/seed/php-model/simple-fhir/src/Patient.php @@ -3,11 +3,14 @@ namespace Seed; use Seed\Core\Json\SerializableType; +use Seed\Traits\BaseResource; use Seed\Core\Json\JsonProperty; use Seed\Core\Types\ArrayType; class Patient extends SerializableType { + use BaseResource; + /** * @var string $resourceType */ @@ -31,6 +34,9 @@ class Patient extends SerializableType * resourceType: string, * name: string, * scripts: array